[
  {
    "path": ".github/ISSUE_TEMPLATE/ask-question.md",
    "content": "---\nname: 提问与交流\nabout: 对框架使用过程中遇到的问题、不清楚或模糊的地方；询问框架能否支持某类需求\n\n---\n\n### 你的问题 | 使用场景\n\n描述你遇到的问题，或使用场景（询问框架能否满足此类需求）\n\n\n\n### 预期值\n\n期望的预期值\n\n\n\n\n### 实际值\n\n实际值\n\n\n\n\n### 复现步骤\n\n描述复现步骤，并提供复现 demo\n\n\n\n\n### 版本\n\n- ioGame version: \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: bug\nabout: 疑似 bug\n---\n\n### 你的问题\n\n描述你遇到的问题\n\n\n\n### 预期值\n\n期望的预期值\n\n\n\n\n### 实际值\n\n实际值\n\n\n\n### 复现步骤\n\n描述复现步骤，并提供复现 demo\n\n\n\n### 版本\n\n- ioGame version: \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/empty-issues.md",
    "content": "---\nname: empty issues\nabout: 自定义 issues 描述\n---\n\n### 提问\n\n\n\n\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhance-task.md",
    "content": "---\nname: 功能增强建议\nabout: 对框架功能增强、新增...等相关建议\n---\n\n### 新增功能的使用场景\n\n描述功能的使用场景、作用；或描述功能所带来的好处\n\n\n\n"
  },
  {
    "path": ".gitignore",
    "content": "### other dir ###\nclasses/\n.temp/\nlogs/\nlog/\n.svn/\nsvn/\nlib/\n.act.*\nsystem/\n\nHELP.md\n!**/src/main/**\n!**/src/test/**\n\n*.class\n\n### jreble config ###\nrebel.xml\n\n### Package Files ###\n*.jar\n*.war\n*.ear\n*.zip\n\n# tcp 与前端通讯的 api\ntcp-api/\n\n# gradle #\nbuild/\n.gradle/\nout/\n\n### maven ###\ntarget/\n!.mvn/wrapper/maven-wrapper.jar\n.mvn\nmvnw\nmvnw.cmd\n### intellj file type ###\nbin/\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\n/nbproject/private/\n/nbbuild/\n/dist/\n/nbdist/\n/.nb-gradle/\n\n### VS Code ###\n.vscode/\n\n### STS ###\n.apt_generated\n.factorypath\n.settings\n.springBeans\n.sts4-cache\n\n### eclipse file type ###\n.settings/\n.classpath\n.project\n\n### ---- Mac OS X ###\n.DS_Store\nIcon?\n\n### ---- Windows ###\n\n### Windows image file caches ###\nThumbs.db\n\n### Folder integration file ###\nDesktop.ini\n\n### ---- Javadoc ###\ndocs.tar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n/target/\n\ndoc_game.txt\n.claude\n"
  },
  {
    "path": "BACKERS.md",
    "content": "#### 支持名单\n\n通过 wx、alipay 支付时，不能看见全称，下面使用 '-' 来代替 *。\n\n\n\n#### 2025\n\n##### 小星星\n\n\n| 称号 | 名单                                             |\n| ---- | ------------------------------------------------ |\n| ⭐⭐   | wuhudsm(admin@playdnf.com)、65714050@qq.com、M-x |\n\n\n\n##### 感谢支持\n\nZhangSir、-鸟\n\n---\n\n\n\n#### 2024\n\n##### 小星星\n\n\n| 称号 | 名单 |\n| ------------- | -------------------------------- |\n| ⭐⭐ | 刘先生、wells974、404 Not Found（342644552）、山里人（higaojun@qq.com ） |\n| ⭐    | 金银花、子在川上（captainl1993@126.com） |\n\n\n\n##### 感谢支持\n\nAlan、Y-g、angelhappyboy、黑天小飞侠\n\n---\n\n\n\n#### 2023\n\n##### 小星星\n\n\n| 称号 | 名单 |\n| ------------- | -------------------------------- |\n| ⭐⭐⭐⭐⭐⭐⭐ | 赵少 |\n| ⭐⭐⭐ | Lei Ante |\n| ⭐⭐ | 张松林、-辉、北极光 |\n| ⭐    | -晖、Bill、明亮北极星        |\n\n\n\n\n##### 感谢支持 \n\nbutxx、玩皮猫、v-v、b-e、M-a、j-s、漫步、森、葱花蛋炒饭、小许\n\n---\n\n\n\n\n#### 2022\n\n##### 小星星\n\n| 称号 | 名单 |\n| ------------- | -------------------------------- |\n| ⭐⭐ | 米斯特姚、WX768925736、猿、Micheal |\n| ⭐    | -称                              |\n\n\n\n\n##### 感谢支持 \n\n咖喱、优优、半壁、J-n、华"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "<h2 align=\"center\" style=\"text-align:center;\">\n  ioGame\n</h2>\n<p align=\"center\">\n  <strong>Lock-free, async, event-driven architecture — supports clustering & distribution out of the box, no middleware required</strong>\n  <br>\n  <strong>Build decentralized, auto-scaling, multi-process distributed game servers with ease</strong>\n  <br>\n  <strong>Tiny footprint, blazing-fast startup, low memory usage, zero config files, elegant route-level access control</strong>\n  <br>\n  <strong>Simultaneous support for WebSocket, UDP, TCP and more — with built-in full-link distributed tracing</strong>\n  <br>\n  <strong>One codebase, multiple protocols — seamlessly switch between Protobuf, JSON, and beyond</strong>\n  <br>\n  <strong>Near-native performance — 11.52 million business operations per second in a single thread</strong>\n  <br>\n  <strong>Code-as-documentation, JSR380 validation, assertion + exception patterns — minimal maintenance overhead</strong>\n  <br>\n  <strong>Smart same-process affinity with IDE-friendly code navigation & jump-to-source</strong>\n  <br>\n  <strong>Deploy your way — components run independently or fused together</strong>\n  <br>\n  <strong>Write once, generate client SDKs — interactive code generation for any frontend</strong>\n  <br>\n  <strong>Cross-process, cross-machine communication between logic servers</strong>\n  <br>\n  <strong>Dynamic player-to-server binding</strong>\n  <br>\n  <strong>Plays nicely with any framework</strong>\n  <br>\n  <strong>Feels natural for web MVC developers</strong>\n  <br>\n  <strong>No hard dependency on Spring</strong>\n  <br>\n  <strong>Zero learning curve</strong>\n  <br>\n  <strong>Pure JavaSE</strong>\n</p>\n<p align=\"center\">\n\t<a href=\"http://game.iohao.com\">https://iohao.github.io/game</a>\n</p>\n<p align=\"center\">\n\t<a target=\"_blank\" href=\"https://www.oracle.com/java/technologies/downloads/#java21\">\n\t\t<img src=\"https://img.shields.io/badge/JDK-21-success.svg\" alt=\"JDK 21\" />\n\t</a>\n\t<br>\n\t<a target=\"_blank\" href=\"https://www.gnu.org/licenses/agpl-3.0.txt\">\n\t\t<img src=\"https://img.shields.io/:license-AGPL3.0-blue.svg\" alt=\"AGPL3.0\" />\n\t</a>\n\t<br />\n\t<a target=\"_blank\" href='https://gitee.com/iohao/ioGame'>\n\t\t<img src='https://gitee.com/iohao/ioGame/badge/star.svg' alt='gitee star'/>\n\t</a>\n\t<a target=\"_blank\" href='https://github.com/iohao/ioGame'>\n\t\t<img src=\"https://img.shields.io/github/stars/iohao/ioGame.svg?logo=github\" alt=\"github star\"/>\n\t</a>\n  <br />\n\t<a target=\"_blank\" href='https://app.codacy.com/gh/iohao/ioGame/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade'>\n\t\t<img src=\"https://app.codacy.com/project/badge/Grade/4981fff112754686baad7442be998b17\" alt=\"code quality\"/>\n</a>\n</p>\n<hr />\n\nDocumentation: https://iohao.github.io/game/docs/intro\n\n## Vision\nMake game server development effortless. We're here to change the industry — lowering the barrier to entry and making game development tools truly accessible to everyone.\n\n\n## Open, Free & Developer-Friendly License\n- There is no commercial edition — never has been, never will be. Every feature is open source.\n- We're committed to at least **ten years** of active maintenance, starting from 2022-03-01.\n- ioGame is a lightweight networking framework built for **online games, IoT, internal systems**, and any scenario that needs persistent connections. The source code is fully open, documentation is free, and usage costs nothing (subject to license terms).\n\n<hr/>\n\n**Why AGPL 3.0?**\n\nioGame is released under the [AGPL 3.0](https://www.gnu.org/licenses/agpl-3.0.txt) license. Projects built with ioGame under this license are free of charge.\n\nWe chose AGPL 3.0 because it's **fairer to developers**. It ensures that project ownership is shared — meaning that even if a developer leaves a company, they retain legitimate rights to the project they helped build.\n\nContrast this with permissive licenses like Apache 2.0 or MIT, where developers who leave a company lose all control over the project and get nothing in return. We've all seen it: developers pour their hearts into a project through countless late nights and weekends, only to be let go right before — or just after — launch, watching their hard work become someone else's asset.\n\nUnder AGPL 3.0, shared ownership means developers are genuinely motivated to invest in and polish their work.\n\n\n\n## Startup Showcase\n\nioGame is impressively lean:\n- **Memory**: Minimal footprint.\n- **Startup**: Applications typically boot in under **1 second**.\n- **Package size**: ~**15 MB** as a jar.\n\n![](https://iohao.github.io/game/assets/images/start-cc4b7973e832e31c6c5bf50af83aabeb.png)\n\n\n\n## What is ioGame?\n\nLooking for a game server framework that's **high-performance, stable, easy to use, with built-in load balancing, clean architecture (no class explosion), cross-process communication, decentralized clustering, auto-scaling, and stateful multi-process distribution**? Meet ioGame — a Java networking framework designed exactly for this.\n\n\n\nioGame is a lightweight networking framework built for **online games, IoT, internal systems**, and any scenario that needs persistent connections.\n\n\n\n**Key Features at a Glance:**\n\n> 1. Truly lightweight — lock-free, async, event-driven from the ground up.\n> 2. Small package, low memory, fast startup.\n> 3. Pure JavaSE — integrates effortlessly with Spring, Vert.x, Quarkus, Solon, and others.\n> 4. **Zero learning curve.** If you know basic Java or web MVC, you already know how to use ioGame. No game dev experience required.\n> 5. Architecturally eliminates the **N×N scaling problem** that plagues traditional frameworks.\n> 6. **No third-party dependencies** for clustering & distribution — just a JVM is all you need.\n> 7. Three-part architecture: External Server + Broker (Gateway) + Game Logic Server — each can run **independently or fused together**, adapting to **any game type**.\n> 8. Fully **dynamic scaling** — add or remove External Servers, Logic Servers, and Brokers on the fly.\n> 9. **Multi-server, single-process** mode for development — debug distributed systems as easily as a monolith.\n> 10. Logic Servers can run standalone, **enabling true modularization**.\n> 11. Built-in **full-link distributed tracing**.\n> 12. Rich communication primitives — logic servers can talk across machines seamlessly.\n> 13. MVC-style coding with non-intrusive Java Beans — effectively **prevents class explosion**.\n> 14. Built-in **per-player thread safety** — concurrency handled for you.\n> 15. **One codebase** supports TCP, WebSocket, UDP simultaneously — no code changes needed. Extensible to KCP, QUIC, and beyond.\n> 16. **One codebase** for switching data protocols — Protobuf, JSON, and more. Extensible.\n> 17. **Hot-swap protocols** — add or remove protocols without restarting the gateway or External Server. No player disconnections.\n> 18. Auto-boxing/unboxing of primitives in actions — solves the [protocol fragment](https://iohao.github.io/game/docs/manual/protocol_fragment) problem.\n> 19. Pluggable, extensible **plugin system**.\n> 20. Deploy as **single-process** or **multi-process across machines** — switch freely without code changes.\n> 21. Logic Servers never expose ports — **immune to port-scanning attacks** by design.\n> 22. Built-in **stress testing & simulation module** with real network conditions, continuous interaction, and automation support.\n> 23. Sync, async, and async-callback methods for inter-service communication.\n> 24. **Distributed event bus** (like MQ / Redis pub-sub — works across machines and processes).\n> 25. Elegant **route-level access control**.\n> 26. Intelligent **same-process affinity**.\n> 27. JSR380 validation + assertions + exceptions = **less boilerplate, fewer bugs**.\n> 28. **Write once, generate everywhere** — produce unified interactive SDKs for Godot, UE, Unity, Cocos Creator, Laya, React, Vue, Angular, and more. Massive productivity boost.\n\n\n\nPackaging, memory, and startup are all best-in-class: jar size ~**15 MB**, startup typically under **1 second**, low memory footprint.\n\n\n\n**Ecosystem integration** is straightforward — [Spring integration](https://iohao.github.io/game/docs/manual/integration_spring) takes just 4 lines of code. Beyond Spring, ioGame plays well with Vert.x, Quarkus, Solon, and any other framework, letting you tap into their ecosystems.\n\n\n\n**Zero learning curve.** If you know basic Java or web MVC patterns, you're ready. No game development background needed.\n\n\n\n**Clean coding style.** ioGame provides MVC-like conventions with non-intrusive Java Beans, effectively **preventing class explosion**. Sync, async, and callback methods are available for inter-service calls — resulting in elegant code with full-link tracing baked in.\n\n\n\n**Write once, connect everywhere.** ioGame generates client interaction code automatically, dramatically cutting client-side workload. Write your Java code once and generate unified SDKs for [Godot](https://godotengine.org/), [UE](https://www.unrealengine.com/), [Unity](https://unity.com/), [Cocos Creator](https://www.cocos.com/), [Laya](https://layaair.layabox.com/#/), [React](https://react.dev/), [Vue](https://vuejs.org/), [Angular](https://angular.dev/), and more. Supports [code generation](https://iohao.github.io/game/docs/examples/code_generate) in C#, TypeScript, GDScript, and C++ — fully extensible.\n\n\n\n**No N×N headaches.** Traditional architectures rely on Redis, MQ, ZooKeeper, and other middleware to scale — hardly \"lightweight.\" ioGame solves the [N×N problem](https://iohao.github.io/game/docs/overall/legacy_system) architecturally, without external dependencies.\n\n\n\n**Truly lightweight.** No third-party middleware or database is needed for clustering and distribution — just a JVM. This simplifies usage and slashes deployment and maintenance costs. A single dependency gives you the entire framework — no Nginx, Redis, MQ, MySQL, ZooKeeper, or Protobuf compiler to install.\n\n\n\n**Flexible architecture.** ioGame's [three-part design](https://iohao.github.io/game/docs/overall/deploy_flexible) — External Server, Broker (Gateway), Game Logic Server — can run independently or merged together, adapting to **any game type** simply by adjusting deployment. These changes are trivial and never break existing code.\n\n\n\n**Dynamic scaling.** External Servers, Logic Servers, and Brokers all support live addition and removal. Scale up or down as player counts change. The architecture also enables **zero-downtime updates**: spin up new servers (A-3, A-4) with your latest features, then gracefully retire the old ones (A-1, A-2) — players never notice.\n\n\n\n**Decentralized clustering.** The Broker (Gateway) uses a [masterless, self-organizing cluster design](https://iohao.github.io/game/docs/examples/server/example_broker_cluster) — all nodes are equal and autonomous with no single point of failure. The cluster **auto-manages and elastically scales**, maintaining load balance and consistency as nodes join or leave.\n\n\n\n**Distributed by design.** Logic servers are organized into distinct layers — [External Servers](https://iohao.github.io/game/docs/overall/external_intro), [Game Logic Servers](https://iohao.github.io/game/docs/overall/logic_intro) — each with clear responsibilities and interfaces. This improves readability, maintainability, and enables effortless **horizontal scaling**.\n\n\n\n**Developer-friendly distributed development.** Distributed apps usually mean juggling multiple processes, making debugging painful. Most frameworks can't solve this — **ioGame can.** Multi-server single-process mode lets you develop and debug distributed systems as if they were monoliths.\n\n\n\n**Modular ecosystem.** [Game Logic Servers can run standalone](https://iohao.github.io/game/docs/manual_high/your_ecology) — just plug into the Broker to provide services. Build reusable, **componentized logic servers** — Guild, Friends, Login, Lottery, Announcements, Leaderboards, and more. Benefits include:\n\n1. No redundant development.\n2. Low coupling between modules.\n3. True single-responsibility design — each feature becomes its own **logic server**.\n4. Scale any module independently without code changes.\n5. Build up your own **ecosystem arsenal** of reusable components for competitive advantage.\n6. **Reduced code leak risk.** Monolithic projects put everything in one directory — one leak exposes everything. With modular servers, each developer only accesses their own module.\n7. Admins deploy the gateway and External Server on the internal network; developers code and test their own modules locally. Additional perks:\n    - Client connections survive logic server restarts.\n    - Developers don't need to run each other's modules.\n    - Auto-generated docs handle inter-module integration.\n\n\n\n**Full-link distributed tracing.** Every request gets a [unique trace ID](https://iohao.github.io/game/docs/manual/trace) recorded across logs — filter by ID to instantly find what you need. ioGame's tracing works **across machines and processes**: from request entry to completion, every logic server touched is precisely recorded.\n\n\n\n**Rich communication models.** While most frameworks only offer push/broadcast, ioGame provides a complete set of [communication patterns](https://iohao.github.io/game/docs/manual/communication_model) — all supporting cross-process, cross-machine communication with full-link tracing:\n\n- **Client-facing models:**\n    - [request/response](https://iohao.github.io/game/docs/communication/request_response)\n    - request/void (fire-and-forget)\n    - request/broadcast\n    - [broadcast](http://localhost:3000/docs/communication/broadcast) (server push)\n- **Internal (server-to-server) models:**\n    - [request/response](https://iohao.github.io/game/docs/communication/request_response)\n    - request/void\n    - [request/multiple_response](https://iohao.github.io/game/docs/communication/request_multiple_response) — fan-out to multiple logic servers of the same type\n    - [EventBus](https://iohao.github.io/game/docs/communication/event_bus) — distributed event bus\n    - [ExternalRegion](https://iohao.github.io/game/docs/communication/external_biz_region) — access External Servers\n\n\n\nSince ioGame 21, **virtual threads** are used for blocking inter-service communication, preventing business thread starvation and significantly boosting throughput.\n\n\n\n**Thread safety made easy.** The framework guarantees [per-player thread safety](https://iohao.github.io/game/docs/overall/thread_executor) — even across re-logins, the same thread handles that player's business. For multi-player scenarios (e.g., same room), [Domain Events](https://iohao.github.io/game/docs/extension_module/domain_event) provide a clean solution. ioGame's unique thread executor design makes writing **lock-free concurrent code** straightforward.\n\n\n\n**Lock-free concurrency.** ioGame's elegant thread executor design lets developers write high-concurrency code without locks — naturally and safely.\n\n\n\n**Protocol-agnostic connections.** Use **one codebase** to support TCP, WebSocket, and UDP simultaneously — no modifications needed. Connection types are extensible: when KCP or QUIC support lands, just switch — your business code stays untouched.\n\n\n\n**Flexible data protocols.** [Switch between Protobuf, JSON, and more](https://iohao.github.io/game/docs/manual/data_protocol) with a single line of code. No business method changes required.\n\n\n\n**Hot-swap protocols.** Add or remove protocols **without restarting** the External Server or Broker — no player disconnections, no fleet-wide restarts.\n\n\n\n**Protocol fragment solution.** Actions auto-box and unbox primitive types, solving the [protocol fragment](https://iohao.github.io/game/docs/manual/protocol_fragment) problem while making business code cleaner and boosting developer productivity.\n\n\n\n**[Same-process affinity](https://iohao.github.io/game/docs/manual_high/same_process).** Within a single process, Netty instances communicate via memory — no network overhead, blazing-fast data transfer. The framework intelligently routes requests to same-process logic servers first, falling back to other processes/machines only when needed.\n\n\n\n**Great developer experience.** ioGame ships with [JSR380 validation](https://iohao.github.io/game/docs/core/jsr380), [assertions + exception handling](https://iohao.github.io/game/docs/manual/assert_game_code), [code navigation](https://iohao.github.io/game/docs/core_plugin/action_debug), auto-boxing for primitives, and more — all designed to keep your business code clean and concise.\n\n\n\n**Extensible [plugin system](https://iohao.github.io/game/docs/manual/plugin_intro).** Built-in plugins include DebugInOut, action call statistics, thread monitoring, time-bucketed call analytics, and more. Combine monitoring plugins to catch **performance issues during development** — find and fix problems before they reach production.\n\n\n\n**Flexible deployment.** Run as a **single process** during development, deploy as **multi-process across machines** in production — switch freely without changing a line of code.\n\n\n\n**Secure by design.** Logic Servers [never expose ports](https://iohao.github.io/game/docs/overall/legacy_system#Usage-Management) — **port-scanning attacks are impossible.** No need to manage per-service ports or cloud firewall rules. This entire category of ops headaches simply **disappears**.\n\n\n\n**Realistic testing.** The [stress test & simulation module](https://iohao.github.io/game/docs/extension_module/simulation_client) goes beyond unit tests. It simulates real network conditions with continuous, interactive server communication and full automation support. Great for complex scenario testing and load validation.\n\n\n\n**Cost-effective at every stage.** ioGame reduces costs across learning, development, testing, deployment, scaling, and beyond. With equal resources, ioGame gives your team a competitive edge — and protects you from building value that only benefits others. See the full [cost analysis](https://iohao.github.io/game/services/cost_analysis).\n\n\n\n**Well-organized projects.** ioGame's thoughtful **route design** and elegant [access control](https://iohao.github.io/game/docs/external/access_authentication) naturally produce clean, maintainable codebases. Combined with [code organization conventions](https://iohao.github.io/game/docs/manual_high/code_organization), handoffs and long-term maintenance become much smoother. The deeper you go, the more you'll appreciate this.\n\n\n\n**Modern Java, modern performance.** ioGame requires **JDK 21+**, giving you access to **Generational ZGC** with **sub-millisecond** pause times and modern syntax. GC pauses become invisible — no stuttering, no crashes — like having a JVM tuning expert on your team.\n\n\n\n**In short**, ioGame is purpose-built for online game development. It lets you create high-performance, low-latency, easily scalable game servers while saving time and resources. The framework handles the complex, repetitive infrastructure so you can focus on what matters — your game. It provides **clear structural organization** for modules and development workflows, reducing long-term maintenance costs.\n\n\n\nWe believe you now have a solid overview of ioGame. There are many more features to discover as you dive deeper. Thank you for reading — we look forward to seeing what you build!\n\n---\n\n## Write Once, Generate Everywhere — A Massive Productivity Boost\n\nioGame is built around the principle of **code-as-documentation** and **methods-as-interfaces**.\n\n\n\n**Write once** means writing your Java business code a single time. **Generate everywhere** means automatically producing client interaction code for any frontend project.\n\n\n\nWrite your Java code once and generate unified interaction interfaces for [Godot](https://godotengine.org/), [UE](https://www.unrealengine.com/), [Unity](https://unity.com/), [CocosCreator](https://www.cocos.com/), [Laya](https://layaair.layabox.com/#/), [React](https://react.dev/), [Vue](https://vuejs.org/), [Angular](https://angular.dev/), and more.\n\n\n\nioGame generates **action, broadcast, and error code** interfaces for any frontend project. Write your business logic once — it works with all these game engines and modern frontend frameworks simultaneously.\n\n\n\n**Why generated client code matters:**\n\n1. **Eliminates boilerplate.** Client developers no longer write mountains of template code.\n2. **Crystal-clear semantics.** Generated interfaces explicitly define parameter types and whether to expect a response — no guesswork.\n3. **Type-safe parameters.** Precise interface definitions mean type-safe method signatures, fewer security risks, and **fewer integration bugs**.\n4. **Code is the documentation.** Generated code includes docs and usage examples — **zero learning cost**, even for newcomers.\n5. **Focus on business logic.** Client developers can ignore server communication plumbing and **spend their time on what matters**.\n6. **Smooth integration.** Using generated code feels **as natural as calling a local method** — minimal cognitive overhead for both teams.\n7. **Interface-oriented, not protocol-oriented.** A modern approach that replaces the traditional protocol-centric integration workflow.\n8. **Always in sync.** When your Java code changes, documentation and interfaces update automatically — **no separate docs to maintain**.\n\n\n\n## Architecture Overview\n\n> Lock-free, async, event-driven architecture. Truly lightweight — build a clustered, distributed game server with zero middleware.\n>\n> Decentralized cluster nodes, automated clustering, built-in load balancing, distributed deployment, dynamic machine scaling.\n\n| Component                                                    | Scaling      | Responsibility                   |\n| ------------------------------------------------------------ | ------------ | -------------------------------- |\n| **ExternalServer** — [External Server](https://iohao.github.io/game/docs/overall/external_intro) | Distributed  | Player connections & interaction |\n| **GameLogicServer** — [Game Logic Server](https://iohao.github.io/game/docs/overall/logic_intro) | Distributed  | Business logic processing        |\n| **BrokerCluster** — [Broker (Gateway)](https://iohao.github.io/game/docs/overall/broker_intro) | Clustered    | Request scheduling & forwarding  |\n\n![ioGame](https://iohao.github.io/game/assets/images/ioGame-2cd7572c6c81afd341b7d2e9d703bf65.svg)\n\nFor details, see the [Architecture Guide](https://iohao.github.io/game/docs/overall/architecture_intro).\n\n------\n\n> The Broker (Gateway) runs as a **cluster** — typically stateless, focused on scheduling and forwarding.\n>\n> External Servers and Game Logic Servers use a **distributed** model, supporting multiple instances of the same type. When player counts grow, simply spin up more Logic Servers.\n>\n> **Example:** Two type-A Logic Servers (A-1, A-2) share requests via the gateway's random load-balancing strategy.\n>\n> Both External Servers and Logic Servers support dynamic addition and removal. **Zero-downtime updates** are built in: launch A-3 and A-4 with new features, then gracefully retire A-1 and A-2 — players won't notice a thing.\n>\n> The framework also supports [dynamic player-to-server binding](https://iohao.github.io/game/docs/manual/binding_logic_server) — once bound, all subsequent requests from that player route to the same Logic Server.\n>\n> **Beyond gaming:** ioGame works great for IoT too. Replace \"players\" with \"devices\" in the diagram — the architecture handles hundreds of millions of connections. IoT companies have been using ioGame successfully since 2022.\n\n\n\n**External Server**\n\nThe External Server manages persistent player connections. Say one server supports up to 5,000 connections — when you hit 7,000, just add another External Server to distribute the load.\n\nScaling External Servers provides natural load balancing and traffic control under high concurrency. Thanks to trivial scaling, supporting **millions** of concurrent players is entirely achievable.\n\nEven with multiple External Servers, developers don't need to track which server a player is connected to — broadcasts and pushes reach every player automatically. From the player's perspective, there's only one server. From the developer's perspective, the same is true.\n\nWondering about max connections per External Server? That's a Netty question — because under the hood, that's exactly what it is. If you know Netty, extending the External Server will feel second nature.\n\n\n\n## Quick Start\n\nHere's a simplified view of how a game engine interacts with the game server:\n\n![Business Interaction Diagram](https://iohao.github.io/game/assets/images/introduction_quick-6b29dfc678257db43afb10939356edb6.jpeg)\n\n> The game frontend and server communicate **bidirectionally**, exchanging business data encoded/decoded via `.proto` files. Protocol Buffers (PB) is currently the best choice for game data serialization, though JSON, XML, or custom formats are also supported since everything is transmitted as binary.\n>\n> **Game frontends** can be [Godot](https://godotengine.org/), [Unity](https://unity.cn/), [UE](https://www.unrealengine.com/zh-CN/), [Cocos Creator](https://www.cocos.com/), [Laya](https://layaair.layabox.com/#/), [FXGL](https://github.com/AlmasB/FXGL), or any other engine. The engine handles rendering; data exchange happens over TCP, UDP, etc.\n\n\n\n**Data Protocol**\n\nDefine two simple data protocols for client-server communication. These are jprotobuf objects — a simplified wrapper around Google Protobuf with equivalent performance.\n\nThink of them as DTOs — carriers for business data:\n\n```java\n@ProtobufClass\npublic class LoginVerifyMessage {\n    public String jwt;\n}\n\n@ProtobufClass\npublic class UserMessage {\n    public String name;\n}\n```\n\n\n\n**Action**\n\nHere's your server-side business logic. This code simultaneously supports TCP, WebSocket, and UDP — no changes needed:\n\n```java\n@Slf4j\n@ActionController(1)\npublic class DemoAction {\n    @ActionMethod(0)\n    public UserMessage here(LoginVerifyMessage message) {\n        var userMessage = new UserMessage();\n        userMessage.name = \"Michael Jackson, \" + message.jwt;\n        return userMessage;\n    }\n}\n```\n\n\n\nEach method (like `here`) is an [Action](https://iohao.github.io/game/docs/manual/action) — a unit of business logic.\n\nMethod parameters receive data from the frontend. The return value is automatically sent back to the client. You don't need to understand framework internals.\n\nNotice how this looks just like ordinary Java? That's intentional — and it **prevents class explosion**. If your job is writing game logic, your ioGame learning journey can stop right here.\n\n\n\n**Game programming really is this simple.**\n\n\n\n**Q: Am I ready to start building a game server?**\n\n> Yes. Yes you are.\n\n\n\n**Console Output Example**\n\nWhen an action is called, the console logs:\n\n```text\n┏━━━━━ Debug. [(DemoAction.java:5).here] ━━━━━ [cmd:1-0 65536] ━━━━━ [xxxLogicServer - id:[76526c134cc88232379167be83e4ddfc]\n┣ userId: 1\n┣ Params: message : LoginVerifyMessage(jwt=hello)\n┣ Response: UserMessage(name=Michael Jackson, hello)\n┣ Time: 1 ms (total business method execution time)\n┗━━━━━ [ioGameVersion] ━━━━━ [Thread:User-8-2] ━━━━━━━ [traceId:956230991452569600] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n```\n\n**What each field means:**\n\n- **Debug** `[(DemoAction.java:5).here]` — The action that ran, with line number. Click `DemoAction.java:5` in your IDE to jump straight to the code.\n- **userId** — The requesting player's ID.\n- **Params** — Input from the frontend.\n- **Response** — The return value, automatically pushed to the client.\n- **Time** — Execution duration — use this to spot and optimize slow logic.\n- **cmd** — The [route](https://iohao.github.io/game/docs/manual/cmd) (unique address) for this action.\n- **ioGameVersion** — Current framework version.\n- **Thread** — The thread that executed this action.\n- **traceId** — Unique per-request ID for distributed tracing.\n- **Logic Server** — Which Logic Server handled the request.\n\nThis visibility eliminates the most common time sinks in game development:\n\n- \"Did the client actually send the data?\" *(Parameter issues)*\n- \"Did the server actually respond?\" *(Response issues)*\n- \"Why is the client not getting responses?\" *(Timing issues)*\n\nCode navigation lets developers jump to any business method instantly — invaluable in team settings for understanding and modifying execution flow.\n\n\n\n## Who is ioGame For?\n\n1. Web developers curious about game server development.\n2. Developers new to the game industry.\n3. Anyone interested in game development — no prior experience needed.\n4. Learners who want to see design patterns applied in practice.\n5. Developers open to modern approaches.\n6. Anyone ready to leave legacy codebases behind.\n\n\n\nAt least one year of hands-on programming experience is recommended.\n"
  },
  {
    "path": "README_CN.md",
    "content": "<h2 align=\"center\" style=\"text-align:center;\">\n  ioGame\n</h2>\n<p align=\"center\">\n  <strong>无锁异步化、事件驱动的架构设计；轻量级，无需依赖任何第三方中间件或数据库就能支持集群、分布式</strong>\n  <br>\n  <strong>通过 ioGame 可以很容易的搭建出一个集群无中心节点、集群自动化、多进程的分布式游戏服务器</strong>\n  <br>\n  <strong>包体小、启动快、内存占用少、更加的节约、无需配置文件、提供了优雅的路由访问权限控制</strong>\n  <br>\n  <strong>可同时支持多种连接方式：WS、UDP、TCP...等；框架已支持全链路调用日志跟踪特性</strong>\n  <br>\n  <strong>让开发者用一套业务代码，能轻松切换和扩展不同的数据协议：Protobuf、JSON</strong>\n  <br>\n  <strong>近原生的性能；业务框架在单线程中平均每秒可以执行 1152 万次业务逻辑</strong>\n  <br>\n  <strong>代码即联调文档、JSR380验证、断言 + 异常机制 = 更少的维护成本</strong>\n  <br>\n  <strong>框架具备智能的同进程亲和性；开发中，业务代码可定位与跳转</strong>\n  <br>\n  <strong>架构部署灵活性与多样性：既可相互独立，又可相互融合</strong>\n  <br>\n  <strong>一次编写到处对接，能为客户端生成可交互的代码</strong>\n  <br>\n  <strong>逻辑服之间可相互跨进程、跨机器进行通信</strong>\n  <br>\n  <strong>支持玩家对游戏逻辑服进行动态绑定</strong>\n  <br>\n  <strong>能与任何其他框架做融合共存</strong>\n  <br>\n  <strong>对 webMVC 开发者友好</strong>\n  <br>\n  <strong>无 spring 强依赖</strong>\n  <br>\n  <strong>零学习成本</strong>\n  <br>\n  <strong>javaSE</strong>\n</p>\n<p align=\"center\">\n\t<a href=\"http://game.iohao.com\">https://iohao.github.io/game</a>\n</p>\n<p align=\"center\">\n\t<a target=\"_blank\" href=\"https://www.oracle.com/java/technologies/downloads/#java21\">\n\t\t<img src=\"https://img.shields.io/badge/JDK-21-success.svg\" alt=\"JDK 21\" />\n\t</a>\n\t<br>\n\t<a target=\"_blank\" href=\"https://www.gnu.org/licenses/agpl-3.0.txt\">\n\t\t<img src=\"https://img.shields.io/:license-AGPL3.0-blue.svg\" alt=\"AGPL3.0\" />\n\t</a>\n\t<br />\n\t<a target=\"_blank\" href='https://gitee.com/iohao/ioGame'>\n\t\t<img src='https://gitee.com/iohao/ioGame/badge/star.svg' alt='gitee star'/>\n\t</a>\n\t<a target=\"_blank\" href='https://github.com/iohao/ioGame'>\n\t\t<img src=\"https://img.shields.io/github/stars/iohao/ioGame.svg?logo=github\" alt=\"github star\"/>\n\t</a>\n  <br />\n\t<a target=\"_blank\" href='https://app.codacy.com/gh/iohao/ioGame/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade'>\n\t\t<img src=\"https://app.codacy.com/project/badge/Grade/4981fff112754686baad7442be998b17\" alt=\"code quality\"/>\n</a>\n</p>\n<hr />\n\nDocumentation: https://iohao.github.io/game/docs/intro\n\n## 愿景\n让网络游戏服务器的编程变得轻松简单。 改变行业现状，降低使用难度，让游戏开发领域的生产资料公有制！\n\n\n## 开放、自由、友好的开源协议\n- 过去、现在、将来都不会有商业版本，所有功能全部开源。\n- 承诺项目的维护周期是十年起步， 2022-03-01 起，至少十年维护期。\n- ioGame 是一个轻量级的网络编程框架，适用于**网络游戏服务器、物联网、内部系统**及各种需要长连接的场景。 源码完全开放、最新文档阅读完全开放、提供高质量的使用文档，使用完全自由、免费（遵守开源协议）。\n\n<hr/>\n\n**友好的开源协议**\n\nioGame 使用 [AGPL3.0](https://www.gnu.org/licenses/agpl-3.0.txt) 开源协议，开发者在使用时需要遵守该协议。 在该协议下开发的项目是免费的，没有任何成本。\n\n同时，该协议对开发者更加友好，它确保了项目所有权在资本家和开发者之间共享。 这意味着，即使开发者带着项目离开公司或自行运营，也依然是合法的。\n\n与 Apache 2.0、MIT 等其他协议不同，那些协议下，一旦开发者离开公司，便会彻底失去对项目的控制权，难以获得任何实质性回报。 尤其是在开发阶段，程序员辛勤付出、996 加班，但当项目即将上线或刚上线时，却可能面临被裁的风险。 最终，他们只能眼睁睁看着自己倾注心血的成果被资本家掌控，而自己却一无所有。\n\n因此，采用 AGPL 3.0 协议的项目，由于开发者能共享所有权，更能激励程序员全身心投入，用心打磨项目。\n\n\n\n## 启动展示\n\nioGame 在内存占用、启动速度、打包等方面也是优秀的。\n- 内存方面：内存占用小。\n- 启动速度方面：应用通常会在 **0.x 秒**内完成启动。\n- 打包方面：打 jar 包后大约 **15MB** 。\n\n![](https://iohao.github.io/game/assets/images/start-cc4b7973e832e31c6c5bf50af83aabeb.png)\n\n\n\n## 介绍\n\n你是否想要开发一个**高性能、稳定、易用、自带负载均衡、避免类爆炸设计、可跨进程跨机器通信、集群无中心节点、集群自动化、有状态多进程的分布式的**网络编程服务器呢？ 如果是的话，这里向你推荐一个由 java 语言编写的网络编程框架 ioGame。\n\n\n\nioGame 是一个轻量级的网络编程框架，适用于**网络游戏服务器、物联网、内部系统**及各种需要长连接的场景。\n\n\n\n**ioGame 有以下特点：**\n\n> 1. 真轻量级、无锁异步化、事件驱动的架构设计。\n> 2. 包体小、内存占用少、启动速度快。\n> 3. ioGame 是纯 javaSE 的，使得 ioGame 能与其他框架方便的进行集成、融合，如 Spring ...等。\n> 4. 在学习成本方面，ioGame 的学习成本非常低，可以说是**零学习成本**，即使没有游戏编程经验，也能轻松上手。开发者只需掌握普通的 java 方法或 webMVC 相关知识，就能用框架开发业务。\n> 5. ioGame 在架构上解决了传统框架所产生的 **N\\*N 问题**。\n> 6. 在轻量级方面，ioGame **不依赖任何第三方**中间件或数据库**就能支持集群、分布式**，只需要 java 环境就可以运行。\n> 7. 在架构灵活性方面，ioGame 的架构由三部分组成：1.游戏对外服、2.Broker（游戏网关）、3.游戏逻辑服。三者既可相互独立，又可相互融合，这意味着使用 ioGame 可以**适应任何类型的游戏**。\n> 8. 架构是可以动态扩缩的，游戏对外服、游戏逻辑服、Broker（游戏网关）都**支持动态增加和减少**。\n> 9. 在分布式开发体验方面，ioGame 支持多服单进程的启动方式，这使得开发者在开发和调试分布式系统时更加简单。\n> 10. 在生态规划方面，游戏逻辑服是支持独立运行，**从而实现功能模块化的可能性**。\n> 11. 具备全链路调用日志跟踪特性。\n> 12. 在通讯方式方面，提供多种通讯方式，且逻辑服之间可以相互跨机器通信。\n> 13. 在编码风格上，提供了类 MVC 的编码风格（无入侵的 Java Bean ），这种设计方式很好的**避免了类爆炸**。\n> 14. 在线程安全方面，框架为开发者解决了单个玩家的**并发问题**。\n> 15. 在连接方式方面，允许开发者**使用一套业务代码**，同时支持 TCP、WebSocket、UDP 等多种连接方式，无需进行任何修改，并且可扩展。\n> 16. 在数据协议方面，ioGame 让开发者**用一套业务代码**，就能轻松切换不同的数据协议，如 Protobuf、JSON 等，并且可扩展。\n> 17. 在增减协议方面，ioGame 可以让你在**新增或减少协议**时，**无需重启**游戏对外服与 Broker（游戏网关）。这样既能避免玩家断线，又能避免因新增、减少协议而重启所有机器的痛点。\n> 18. action 支持自动装箱、拆箱基础类型，用于解决[协议碎片](https://iohao.github.io/game/docs/manual/protocol_fragment)的问题。\n> 19. 业务框架提供了插件机制，插件是可插拨、可扩展的。\n> 20. 在部署方面，ioGame 支持**多服单进程**的方式部署，也支持**多服多进程**多机器的方式部署，在部署方式上可以随意的切换而不需要更改代码。\n> 21. 在安全方面，所有的游戏逻辑服不需要开放端口，**天然地避免了扫描攻击**。\n> 22. 在模拟客户端测试方面，ioGame 提供了压测&模拟客户端请求模块。该模块**可以模拟真实的网络环境**，并且在模拟测试的过程中与服务器的交互是可持续的、可互动的，同时也是支持自动化的。\n> 23. 框架为开发者提供了同步、异步、异步回调的方法，用于逻辑服之间的相互访问。\n> 24. 分布式事件总线支持（类似 MQ、Redis 发布订阅机制，可跨多个机器通信、可跨多个进程通信）。\n> 25. 提供优雅的路由访问权限控制。\n> 26. 具备智能的同进程亲和性。\n> 27. JSR380 验证、断言 + 异常机制 = 更少的维护成本。\n> 28. 一次编写到处对接，提升巨大的生产力，能为各客户端生成可交互的代码。你只需要编写一次 java 代码，就能为 Godot、UE、Unity、CocosCreator、Laya、React、Vue、Angular ...等项目生成统一的交互接口。\n\n\n\nioGame 在打包、内存占用、启动速度等方面也是优秀的。 打 jar 包后大约 **15MB**，应用通常会在 **0.x 秒**内完成启动，内存占用小。\n\n\n\n在生态融合方面，ioGame 可以很方便的[与 Spring 集成](https://iohao.github.io/game/docs/manual/integration_spring)（4 行代码）。 除了 Spring 外，还能与任何其他的框架做**融合**，如 Vert.x、Quarkus、Solon ...等，从而使用其他框架的相关生态。\n\n\n\n在学习成本方面，ioGame 的学习成本非常低，可以说是**零学习成本**，即使没有游戏编程经验也能轻松上手。 开发者只需掌握普通的 java 方法或 webMVC 相关知识，就能使用框架开发业务。\n\n\n\n在编码风格上，ioGame 为开发者提供了类 MVC 的编码风格（无入侵的 Java Bean ），这种设计方式很好的**避免了类爆炸**。 同时，框架为开发者提供了同步、异步、异步回调的方法，用于逻辑服之间的相互访问。 这使得开发者所编写的代码会非常的优雅，并且具备全链路调用日志跟踪。\n\n\n\n与客户端对接方面，ioGame 具备**一次编写到处对接**的能力，为客户端提供了代码生成的辅助功能，能够帮助客户端开发者减少巨大的工作量。 这将意味着，你只需要编写一次 java 代码，就能为 Godot、UE、Unity、CocosCreator、Laya、React、Vue、Angular ...等项目生成统一的交互接口。 ioGame 提供了多种语言的 SDK 支持及相关语言的[代码生成](https://iohao.github.io/game/docs/examples/code_generate)，分别是 C#、TypeScript、GDScript、C++，并支持扩展。\n\n\n\nioGame 在架构上解决了传统框架所产生的 **N\\*N 问题**（[与传统架构对比](https://iohao.github.io/game/docs/overall/legacy_system)）。 传统架构在扩展机器时，需要借助很多第三方中间件，如：Redis、MQ、ZooKeeper ...等，才能满足整体架构的运作。 通常，只要引入了需要安装的中间件才能做到扩展的，那么你的架构或者说框架，基本上与轻量级无缘了。\n\n\n\n在轻量级方面，ioGame **不依赖任何第三方**中间件或数据库**就能支持集群、分布式**，只需要 java 环境就可以运行。 这意味着在使用上简单了，在部署上也为企业减少了部署成本、维护难度。使用 ioGame 时，只需一个依赖即可获得整个框架， 而无需安装其他服务，如： Nginx、Redis、MQ、Mysql、ZooKeeper、Protobuf 协议编译工具 ...等。\n\n\n\n在[架构灵活性](https://iohao.github.io/game/docs/overall/deploy_flexible)方面，ioGame 的架构由三部分组成：1.游戏对外服、2.Broker（游戏网关）、3.游戏逻辑服。 三者既可相互独立，又可相互融合。 这意味着使用 ioGame 可以**适应任何类型的游戏**，因为只需通过调整部署方式，就可以满足不同类型的游戏需求。 在 ioGame 中进行这些调整工作非常简单，而且不会对现有代码产生不良影响。\n\n\n\n架构是可以动态扩缩的，游戏对外服、游戏逻辑服、Broker（游戏网关）都**支持动态增加和减少**。 无论未来玩家数量增加或减少，我们都能够轻松应对。 同时，架构是**支持玩家无感知更新**的，这得益于分布式设计。 举例来说，如果 A 类型的游戏逻辑服需要增加一些新功能，我们可以启动 A-3、A-4 等已经支持了新功能的服务器， 然后逐步将之前的 A-1 和 A-2 下线，从而实现了无感知的更新。\n\n\n\n在集群方面，ioGame 的 Broker （游戏网关）采用无中心节点、[自动化的集群设计](https://iohao.github.io/game/docs/examples/server/example_broker_cluster)，所有节点平等且自治，不存在单点故障。 集群能够**自动管理和弹性扩缩**，节点加入或退出时，能够自动保证负载均衡和数据一致性，不影响服务可用性。\n\n\n\n在分布式方面，ioGame 的逻辑服使用了分布式设计思想，将服务器分为[游戏对外服](https://iohao.github.io/game/docs/overall/external_intro)、[游戏逻辑服](https://iohao.github.io/game/docs/overall/logic_intro)等不同层次， 并且每一层都有明确的职责和接口。这样可以提高代码可读性和可维护性，并且方便进行**水平扩展**。\n\n\n\n在分布式开发体验方面，通常在开发分布式应用时是需要启动多个进程的。 这会让调试与排查问题变得非常困难，从而降低开发者的效率、增加工作量等，这也是很多框架都**解决不了的问题**，但 ioGame 做到了！ ioGame 支持多服单进程的启动方式，这使得开发者在开发和调试分布式系统时更加简单。\n\n\n\n在[生态规划](https://iohao.github.io/game/docs/manual_high/your_ecology)方面，我们的游戏逻辑服是支持独立运行的，只需接入 Broker（游戏网关）上， 就可以为玩家和其他游戏逻辑服提供功能上的扩展与增强。 我们可以将一些**游戏逻辑服组件化**，并制作成相对通用的组件，**从而实现功能模块化的可能性**。这么做有几个优点\n\n1. 避免一些重复开发的工作量。\n2. 减少各功能模块的耦合。\n3. 更符合单一职责的设计，将相对通用的功能扩展成一个个的**功能逻辑服**。如，公会逻辑服、好友逻辑服、登录逻辑服、抽奖逻辑服、公告逻辑服、排行榜逻辑服...等。\n4. 由于模块功能是独立，那么将来可以对任意的功能逻辑服进行扩容，且不需要改动任何代码。\n5. 这些组件化后的功能逻辑服就好比一件件武器，积累得足够多时就形成了自己的生态武器库，可以更好的帮助公司与同行竞争。\n6. **代码泄漏机率更小**。传统的游戏项目通常采用单机结构，把所有的代码放在一个目录中。这样做有很大的风险，因为如果代码泄漏了，就会泄漏整个项目的内容。当功能模块化后，可以让不同的开发人员只负责自己的游戏逻辑服模块，从而避免代码泄漏的风险和影响。\n7. 团队管理员只需要在内网服务器上部署一个游戏网关和游戏对外服，而开发人员就可以在本机上编码和测试自己的游戏逻辑服模块。这样还有以下好处\n    - 游戏客户端不会因为游戏逻辑服的变更或重启而断开连接。\n    - 开发人员不需要启动其他人的游戏逻辑服模块。\n    - 开发人员可以通过 ioGame 自动生成的文档来进行模块间的对接。\n\n\n\nioGame 具备[全链路调用日志跟踪](https://iohao.github.io/game/docs/manual/trace)特性，这在分布式下非常的实用。 该特性为每个请求分配一个唯一标识，并记录在日志中，通过唯一标识可以快速的在日志中过滤出指定请求的信息。 ioGame 提供的全链路调用日志跟踪特性更是强大，**支持跨机器、跨进程**。 简单的说，从玩家的请求进来到结束，无论该请求经过了多少个游戏逻辑服，都能精准记录。\n\n\n\n在通讯方式方面，大部分框架只能支持推送（广播）这一类型的通讯方式。 ioGame 则提供了多种[通讯模型](https://iohao.github.io/game/docs/manual/communication_model)， 通过对各种通讯方式的组合使用，可以简单完成以往难以完成的工作， 并且这些通讯方式都支持跨进程、跨机器通信，且具备全链路调用日志跟踪。\n\n- 在客户端的角度，提供了如下的通讯模型\n    - [request/response](https://iohao.github.io/game/docs/communication/request_response)，请求/响应\n    - request/void，请求/无响应\n    - request/broadcast，请求/广播响应\n    - [broadcast](http://localhost:3000/docs/communication/broadcast)，广播\n- 内部通讯主要用于服务器内部之间的通信，跨服、跨进程通信。提供了如下的通讯模型\n    - [request/response](https://iohao.github.io/game/docs/communication/request_response)，请求/响应\n    - request/void，请求/无响应\n    - [request/multiple_response](https://iohao.github.io/game/docs/communication/request_multiple_response)，同时请求同类型多个游戏逻辑服\n    - [EventBus](https://iohao.github.io/game/docs/communication/event_bus)，分布式事件总线\n    - [ExternalRegion](https://iohao.github.io/game/docs/communication/external_biz_region)，访问游戏对外服\n\n\n\n从 ioGame21 开始，框架添加了虚拟线程的相关支持。 各逻辑服之间通信阻塞部分使用虚拟线程，这样可以很好的避免阻塞业务线程，并大幅提高了框架的吞吐量。\n\n\n\n在线程安全方面，框架为开发者解决了单个玩家的**并发问题**。 即使玩家重新登录后，也会使用相同的线程来消费业务，并推荐使用[领域事件](https://iohao.github.io/game/docs/extension_module/domain_event)来解决同一房间或业务内多个玩家的并发问题。 [框架在线程的扩展性](https://iohao.github.io/game/docs/overall/thread_executor)上提供了友好的支持，开发者可以很容易的编写出无锁并发代码，这得益于 ioGame 独有的线程执行器设计与扩展。 换句话说，你不会因为并发问题烦恼。\n\n\n\n在无锁并发方面，ioGame 提供了优雅、独特的线程执行器设计。通过该特性，开发者能轻易的编写出无锁高并发的代码。\n\n\n\n在连接方式方面，ioGame 允许开发者**使用一套业务代码**，**同时支持**多种连接方式，无需进行任何修改。 ioGame 已经支持了 TCP、WebSocket 和 UDP 连接方式，并且也支持在这几种连接方式之间进行灵活切换。 连接方式是可扩展的，并且扩展操作也很简单，这意味着之后如果支持了 KCP、QUIC， 无论你当前项目使用的是 TCP、WebSocket、UDP，都可以切换成 KCP、QUIC。 即使切换到 KCP、QUIC 的连接方式，现有的业务代码也无需改变。\n\n\n\n在通信协议方面，ioGame 让开发者**用一套业务代码**，就能轻松[切换和扩展不同的数据协议](https://iohao.github.io/game/docs/manual/data_protocol)， 如 Protobuf、JSON 等。只需一行代码，就可以从 Protobuf 切换到 JSON，无需改变业务方法。\n\n\n\n在增减协议方面，ioGame 可以让你在**新增或减少协议**时，**无需重启**游戏对外服与 Broker（游戏网关）。 这样既能避免玩家断线，又能避免因新增、减少协议而重启所有机器的痛点。\n\n\n\n在协议碎片方面，action 支持自动装箱、拆箱基础类型特性，用于解决[协议碎片](https://iohao.github.io/game/docs/manual/protocol_fragment)的问题。 同时该特性除了能使你的业务代码更加清晰以外，还能大幅提高开发者在该环节的生产力。\n\n\n\n在[同进程亲和性](https://iohao.github.io/game/docs/manual_high/same_process)方面，在同一进程内， 不同 Netty 实例之间的通信，是通过内存进行传输的，不需要经过网络传输，数据传输速度极快。 同进程亲和性指的是，优先访问同进程内的游戏逻辑服，当同进程内没有能处理请求的游戏逻辑服时， 才会去其他进程或机器中查找能处理请求的游戏逻辑服。 简单点说，框架对于请求的处理很智能，会优先将请求给同进程内的逻辑服消费。\n\n\n\n在开发体验方面，ioGame 非常注重开发者的开发体验。 框架提供了 [JSR380 验证](https://iohao.github.io/game/docs/core/jsr380)、[断言 + 异常机制](https://iohao.github.io/game/docs/manual/assert_game_code)、[业务代码定位](https://iohao.github.io/game/docs/core_plugin/action_debug)， action 支持自动装箱、拆箱基础类型，用于解决协议碎片的问题 ...等。 诸多丰富的功能，使得开发者的业务代码更加的清晰、简洁。\n\n\n\n业务框架提供了[插件](https://iohao.github.io/game/docs/manual/plugin_intro)机制，插件是可插拨、可扩展的。 框架内置提供了 DebugInOut、action 调用统计、业务线程监控插件、各时间段调用统计插件...等插件。 不同的插件提供了不同的关注点，比如我们可以使用调用、监控等插件相互配合，可以让我们在开发阶段就知道**是否存在性能问题**。 合理利用好各个插件，可以让我们在开发阶段就能知道问题所在，提前发现问题，提前预防问题。\n\n\n\n在部署方面，ioGame 支持**多服单进程**的方式部署，也支持**多服多进程**多机器的方式部署，在部署方式上可以随意的切换而不需要更改代码。 日常中我们可以按照单体思维开发，到了生产可以选择性的使用多进程的方式部署。\n\n\n\n在安全方面，所有的游戏逻辑服[不需要开放端口，天然地避免了扫描攻击](https://iohao.github.io/game/docs/overall/legacy_system#Usage-Management)。 由于不需要为每个逻辑服分配独立的端口，那么我们在使用诸如云服务器之类的服务时，就不需要担心端口开放权限的问题了。 别小看这一个环节，通常这些小细节最浪费开发者的时间。 由于我们不需要管理这些 IP:Port，**这部分的工作量就自然地消失了**。\n\n\n\n在模拟客户端测试方面，ioGame 提供了[压测&模拟客户端请求](https://iohao.github.io/game/docs/extension_module/simulation_client)模块。 此模块是用于模拟客户端，简化模拟工作量，只需要编写对应请求与回调。 除了可以模拟简单的请求外，通常还可以做一些复杂的请求编排，并支持复杂业务的压测。 **与单元测试不同的是，该模块可以模拟真实的网络环境，并且在模拟测试的过程中与服务器的交互是可持续的、可互动的，同时也是支持自动化的**。\n\n\n\n使用 ioGame，可以显著的帮助企业减少巨额成本。 文档中，**成本**关键字提到了很多次，各个阶段均有关联，包括了学习、研发、测试、部署、扩展、投入 ...等各阶段。 在同等资源的竞争下，使用 ioGame 能为公司节省更多的资源，从而提高了自身的生存率。 更重要的是避免了为其他公司做嫁衣的可能性，具体可阅读[成本分析案例](https://iohao.github.io/game/services/cost_analysis)。\n\n\n\n开发者基于 ioGame 编写的项目模块，通常是条理清晰的，得益于框架对**路由的合理设计**，同时也为路由提供了优雅的[访问权限控制](https://iohao.github.io/game/docs/external/access_authentication)。 当我们整理好这些模块后，对于其他开发者接管项目或后续的维护中，会是一个不错的帮助（[代码组织与约定](https://iohao.github.io/game/docs/manual_high/code_organization)）。 或许现阶段你感受不到这块的威力，随着你深入地使用实践就能体会到这么设计的诸多好处与优势。\n\n\n\n开发者基于 ioGame 编写的项目，通常是语法简洁的、高性能的、低延迟的。 框架最低要求使用 **JDK21**，这样即可以让项目享受到**分代 ZGC** 带来的改进，还能享受语法上的简洁。 分代 ZGC 远低于其**亚毫秒级**暂停时间的目标，可以在不影响游戏速度的情况下，清理掉多余的内存。 这样就不会出现卡顿或者崩溃的问题了，相当于在项目中变相的引入了一位 JVM 调优大师。\n\n\n\n综上所述，ioGame 是一个非常适合网络游戏开发的框架。可以让你轻松地创建高性能、低延迟、易扩展的游戏服务器，并且节省时间和资源。 如果你想要快速地开发出令人惊艳的网络游戏，请不要犹豫，立即选择 ioGame 吧！ 框架屏蔽了很多复杂且重复性的工作，并可为项目中的功能模块结构、开发流程等进行**清晰的组织定义**，减少了后续的项目维护成本。\n\n\n\n框架在开发、部署、压测&模拟测试 ...等，各个阶段都提供了很好的支持。 相信你已经对 ioGame 有了一个初步的了解，虽然还有很多丰富的功能与特性没有介绍到，但你可以通过后续的实践过程中来深入了解。 感谢你的阅读，并期待你使用 ioGame 来打造自己的游戏服务器。\n\n---\n\n## 一次编写到处对接，提升巨大的生产力\n\nioGame 是非常注重开发体验的，代码注释即文档、方法即交互接口的原则。\n\n\n\nioGame 具备一次编写到处对接的能力，从而让你们团队提升巨大的生产力。 **一次编写**指的是编写一次 java 业务代码，而**到处对接**则是指为不同的前端项目生成与服务器交互的代码。\n\n\n\n你只需要编写一次 java 代码，就能为 [Godot](https://godotengine.org/)、 [UE](https://www.unrealengine.com/)、 [Unity](https://unity.com/)、 [CocosCreator](https://www.cocos.com/)、 [Laya](https://layaair.layabox.com/#/)、 [React](https://react.dev/)、 [Vue](https://vuejs.org/)、 [Angular](https://angular.dev/) ...等项目生成统一的交互接口\n\n\n\nioGame 能为各种前端项目生成 **action、广播、错误码** 相关接口代码。 这将意味着，你只需要编写一次业务代码，就可以同时与这些游戏引擎或现代化的前端框架交互。\n\n\n\n前端代码生成的几个优势\n\n1. 帮助客户端开发者减少巨大的工作量，**不需要编写大量的模板代码**。\n2. **语义明确，清晰**。生成的交互代码即能明确所需要的参数类型，又能明确服务器是否会有返回值。这些会在生成接口时就提前明确好。\n3. 由于我们可以做到明确交互接口，进而可以明确参数类型。这使得**接口方法参数类型安全、明确**，从而有效避免安全隐患，从而**减少联调时的低级错误**。\n4. 减少服务器与客户端双方对接时的沟通成本，代码即文档。生成的联调代码中有文档与使用示例，方法上的示例会教你如何使用，即使是新手也能做到**零学习成本**。\n5. 帮助客户端开发者屏蔽与服务器交互部分，**将更多的精力放在真正的业务上**。\n6. 为双方联调减少心智负担。联调代码使用简单，**与本地方法调用一般丝滑**。\n7. 抛弃传统面向协议对接的方式，转而**使用面向接口方法的对接方式**。\n8. 当我们的 java 代码编写完成后，我们的文档及交互接口可做到同步更新，**不需要额外花时间去维护对接文档及其内容**。\n\n\n\n## 架构简图\n\n> 无锁异步化、事件驱动的架构设计；真轻量级，无需依赖任何第三方中间件或数据库就能搭建出一个集群、分布式的网络游戏服务器。\n>\n> 集群无中心节点、集群自动化、自带负载均衡、分布式支持、可动态增减机器。\n\n| 名称                                                         | 扩展方式 | 职责             |\n| ------------------------------------------------------------ | -------- | ---------------- |\n| **ExternalServer**，[游戏对外服](https://iohao.github.io/game/docs/overall/external_intro) | 分布式   | 与玩家连接、交互 |\n| **GameLogicServer**，[游戏逻辑服](https://iohao.github.io/game/docs/overall/logic_intro) | 分布式   | 处理具体业务逻辑 |\n| **BrokerCluster**，[Broker（游戏网关）](https://iohao.github.io/game/docs/overall/broker_intro) | 集群     | 调度和转发任务   |\n\n![ioGame](https://iohao.github.io/game/assets/images/ioGame-2cd7572c6c81afd341b7d2e9d703bf65.svg)\n\n更详细的介绍请阅读[架构介绍](https://iohao.github.io/game/docs/overall/architecture_intro)。\n\n------\n\n> 从图中可以看出，游戏网关支持以集群方式启动多个实例。这个设计选择了集群的方式，因为游戏网关通常是无状态的，主要用于调度和转发任务\n>\n> 而游戏对外服、游戏逻辑服使用分布式设计，支持启动多个相同类型的服务。这意味着，当玩家数量增加时，我们可以轻松增加相应类型的游戏逻辑服以处理更多请求。\n>\n> 以游戏逻辑服为例，假设我们启动了两个 A 类型的游戏逻辑服，分别为 A-1 和 A-2。当玩家向 A 类型的游戏逻辑服发起多次请求时，游戏网关会使用默认的随机负载策略将请求分配给 A-1 和 A-2 来处理。\n>\n> 现在我们明白，游戏对外服和游戏逻辑服都支持动态增加和减少。无论未来玩家数量增加或减少，我们都能够轻松应对。架构是**支持玩家无感知更新**的，这得益于分布式设计。举例来说，如果 A 类型的游戏逻辑服需要增加一些新功能，我们可以启动 A-3、A-4 等已经支持了新功能的服务器，然后逐步将之前的 A-1 和 A-2 下线，从而实现了无感知的更新。\n>\n> 此外，框架还支持玩家[动态绑定游戏逻辑服](https://iohao.github.io/game/docs/manual/binding_logic_server)。玩家与游戏逻辑服绑定后，之后的请求都由该游戏逻辑服来处理。\n>\n> 除了游戏之外，ioGame 也适用于物联网相关项目。只需将图中的玩家视为具体的设备，即使存在数亿个设备，ioGame 的架构也可以轻松支持。从 2022 年开始，已经有一些物联网公司开始采用这一解决方案，并得到了很好的体验。\n\n\n\n**游戏对外服**\n\n游戏对外服主要负责与用户（玩家）的长连接，先来个假设，假如我们的一台硬件支持我们建立用户连接的上限是 5000 人， 当用户量达到 7000 人时，我们可以多加一个对外服务器来进行分流减压。\n\n通过增加游戏对外服的数量，可以有效地进行连接的负载均衡和流量控制，使得系统能够更好地承受高并发的压力。 由于游戏对外服扩展的简单性，意味着支持同时在线玩家可以轻松的达到百万、千万甚至更多。\n\n即使我们启动了多个游戏对外服，开发者也不需要关心这些玩家连接到了哪个游戏对外服的问题， 这些玩家总是能接收到广播（推送）消息的，因为框架已经把这些事情给做了。 在玩家的角度我们只有“一个”服务器，同样的，在开发者的角度我们只有“一个”游戏对外服。\n\n通常，有些开发者想知道游戏对外服最大支持多少玩家连接。 关于这个问题，只需要搜索 Netty 相关知识即可，因为游戏对外服本质上是 Netty。\n\n同样的，如果开发者已经熟悉了 Netty 相关知识，那么在游戏对外服的扩展上也会变得非常的容易。\n\n\n\n## 快速入门\n\n下面是游戏引擎与游戏服务器的业务交互简图。\n\n![业务交互简图](https://iohao.github.io/game/assets/images/introduction_quick-6b29dfc678257db43afb10939356edb6.jpeg)\n\n> 抽象的说，游戏前端与游戏服务器的的交互由上图组成。 游戏前端与游戏服务器可以自由地**双向交互**，即发送和接收业务数据。 业务数据由 .proto 文件作为载体，在前端和后端之间进行编码和解码。 .proto 文件是对业务数据的描述载体，定义了数据类型和消息类型，以及它们的属性和规则。\n>\n> 通过这种方式，游戏前端和游戏服务端可以建立连接，并开始相互传递业务数据，处理各自的业务。 以上是对游戏前端与游戏服务器之间交互方式的介绍。 接下来，我们将编写一个简单的游戏业务处理示例，并定制一个适合我们需求的业务数据协议。\n>\n> **协议文件**是对业务数据的描述载体，用于游戏前端与游戏服务器的数据交互。 Protocol Buffers 是 Google 开发的一种数据描述语言，也简称 PB。 协议文件描述还可以是 json、xml 或者任意自定义的，因为最后传输时会转换为二进制，但游戏开发中 PB 是目前的最佳选择。\n>\n> **游戏前端**的展现可以是 [Godot](https://godotengine.org/)、 [Unity](https://unity.cn/)、 [UE](https://www.unrealengine.com/zh-CN/)、 [Cocos Creator](https://www.cocos.com/)、 [Laya](https://layaair.layabox.com/#/)、 [FXGL](https://github.com/AlmasB/FXGL) 或者其他的游戏引擎。 这些游戏引擎只是展现游戏画面的一种形式，数据交互则由通信来完成（TCP、UDP ...等）。\n\n\n\n**数据协议**\n\n现在，我们定义两个数据协议，用于客户端与服务器的数据交互。 这是一个 jprotobuf 的 pb 对象，jprotobuf 是对 google protobuf 的简化使用，性能同等。\n\n\n\n可以把这理解成 DTO 业务数据载体等，其主要目的是用于业务数据的传输。\n\n```java\n@ProtobufClass\npublic class LoginVerifyMessage {\n    public String jwt;\n}\n\n@ProtobufClass\npublic class UserMessage {\n    public String name;\n}\n```\n\n\n\n**Action**\n\n游戏服务器的编程，游戏服务器接收业务数据后，对业务数据进行处理。 下面这段代码可以同时支持 TCP、WebSocket、UDP 通信方式。\n\n```java\n@Slf4j\n@ActionController(1)\npublic class DemoAction {\n    @ActionMethod(0)\n    public UserMessage here(LoginVerifyMessage message) {\n        var userMessage = new UserMessage();\n        userMessage.name = \"Michael Jackson, \" + message.jwt;\n        return userMessage;\n    }\n}\n```\n\n\n\n一个方法（here）在业务框架中表示一个 [Action](https://iohao.github.io/game/docs/manual/action)（业务动作）。\n\n方法声明的参数是用于接收前端传入的业务数据，在方法 return 时，数据就可以被游戏前端接收到。 程序员可以不需要关心业务框架的内部细节。\n\n从上面的示例可以看出，这和普通的 java 类并无区别，同时这种设计方式**避免了类爆炸**。 如果只负责编写游戏业务，那么对于业务框架的学习可以到此为止了。\n\n\n\n**游戏编程就是如此简单！**\n\n\n\n**问：我可以开始游戏服务器的编程了吗？**\n\n> 是的，你已经可以开始游戏服务器的编程了。\n\n\n\n**访问示例（控制台）**\n\n当访问 action 业务方法时，控制台将会打印的日志输出如下\n\n```text\n┏━━━━━ Debug. [(DemoAction.java:5).here] ━━━━━ [cmd:1-0 65536] ━━━━━ [xxx逻辑服 - id:[76526c134cc88232379167be83e4ddfc]\n┣ userId: 1\n┣ 参数: message : LoginVerifyMessage(jwt=hello)\n┣ 响应: UserMessage(name=Michael Jackson, hello)\n┣ 时间: 1 ms (业务方法总耗时)\n┗━━━━━ [ioGameVersion] ━━━━━ [线程:User-8-2] ━━━━━━━ [traceId:956230991452569600] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n```\n\n**控制台打印说明**\n\n- **Debug**. [(DemoAction.java:5).here] : 表示执行业务的是 DemoAction 类下的 here 方法，5 表示业务方法所在的代码行数。在工具中点击控制台的 DemoAction.java:5 这条信息，就可以跳转到对应的代码中（快速导航到对应的代码），这是一个开发良好体验的开始！\n- **userId** : 当前发起请求的 用户 id。\n- **参数** : 通常是游戏前端传入的值。\n- **响应** : 通常是业务方法返回的值 ，业务框架会把这个返回值推送到游戏前端。\n- **时间** : 执行业务方法总耗时，我们可根据业务方法总耗时的时长来优化业务。\n- **路由信息** : [cmd - subCmd][路由](https://iohao.github.io/game/docs/manual/cmd)是唯一的访问地址。\n- **ioGameVersion** : 表示当前所使用的 ioGame 版本。\n- **线程** : 当前执行 action 所使用的线程。\n- **traceId** : 全链路调用日志跟踪 id，每个请求唯一。（该特性在分布式下非常实用）\n- **逻辑服** : 当前游戏逻辑服的 id\n\n有了以上信息，游戏开发者可以很快的定位问题。 如果没有可视化的信息，开发中会浪费很多时间在前后端的沟通上。问题包括：\n\n- 是否传参问题 （游戏前端说传了）\n- 是否响应问题（游戏后端说返回了）\n- 业务执行时长问题 （游戏前端说没收到响应， 游戏后端说早就响应了）\n\n其中代码导航可以让开发者快速的跳转到业务类对应代码中， 在多人合作的项目中可以快速的知道业务经过了哪些方法的执行，使得我们可以快速的进行阅读或修改。\n\n\n\n## 适合人群\n\n1. 长期从事 web 内部系统开发人员， 想了解游戏的。\n2. 刚从事游戏开发的。\n3. 未从事过游戏开发，但却对其感兴趣的。\n4. 对设计模式在实践中的应用有兴趣的学习者。\n5. 可以接受新鲜事物的。\n6. 想放弃祖传代码的。\n\n\n\n推荐实际编程经验一年以上的人员。\n\n"
  },
  {
    "path": "changeLog_ioGame.md",
    "content": "文档与日志\n- [框架版本更新日志](https://iohao.github.io/game/docs/version_log)\n- [ioGame 真.轻量级网络编程框架 - 在线使用文档 ](https://game.iohao.com/)\n\n\n> ioGame 每月会发 1 ~ 2 个版本，通常在大版本内升级总是兼容的，如 21.1 升级到任意 21.x 的高版本。\n\n### 2025-08-11 - v21.30\nhttps://github.com/iohao/ioGame/releases/tag/21.30\n\n**Version update summary**\n\n> 1. fix(generate-code): [#490](https://github.com/iohao/ioGame/issues/490) Fixed the escaping of special characters in the Listener exampleCode for the code generation module. 修复 C# 代码生成时的转义字符\n\n\n### 2025-07-19 - v21.29\nhttps://github.com/iohao/ioGame/releases/tag/21.29\n\n**Version update summary**\n\n> 1. [#473](https://github.com/iohao/ioGame/issues/473) fix(core): Change the method name from DataSelfEncode getEncodeData to encodeData to avoid JSON serialization.\n> 2. fix(doc): [#459](https://github.com/iohao/ioGame/issues/459) Supporting the referencing of classes within a JAR file in a multi-module Gradle environment.\n> 3. feat(generate-code): [#452](https://github.com/iohao/ioGame/issues/452)\n> 4. fix(generate-code): broadcast_action_example_action.txt、broadcast_action_example.txt\n> 5. feat(external): [#469](https://github.com/iohao/ioGame/issues/469) Add HttpFallbackHandler to determine if it's a WebSocket upgrade request.\n> 6. perf(doc): Pre-create the Pb for the BroadcastDocument dataClass.\n\n**[other updates]**\n\nUpgrade reactor-netty 1.2.7、commons-io 2.19.0\n\n\n### 2025-06-17 - v21.28\nhttps://github.com/iohao/ioGame/releases/tag/21.28\n\n**Version update summary**\n\n> 1. feat(room): The room supports convenient operations. \n> 2. Deprecated RoomStatusEnum. \n> 3. feat(room): The room supports convenient broadcastRange. \n> 4. docs(all): Update documentation link (https://iohao.github.io/game). \n> 5. [#451](https://github.com/iohao/ioGame/issues/451) Refactor the usage documentation to use the new access address: https://iohao.github.io/game/ .\n\n------\n\n**[other updates]**\n\n```xml\n<netty.version>4.1.122.Final</netty.version>\n```\n\n------\n\n### 2025-05-09 - v21.27\n\nhttps://github.com/iohao/ioGame/releases/tag/21.27\n\n\n**Version update summary**\n\n> 1. feat(generate-code): #449 Supports GDScript GenerateCode\n> 2. #444 Provides GDScript SDK\n> 3. #448 Provides GDScript Example with ioGame\n> 4. perf(core): ActionCommandDocKit\n\n------\n\n\n\n**feat(generate-code)**: #449 Supports GDScript GenerateCode\n\nAbout examples: https://github.com/iohao/ioGameSdkGDScriptExampleGodot\n\n\n\n```java\npublic final class GenerateTest {\n    // setting root path\n    static String rootPath = \"/Users/join/gitme/ioGame-sdk/\";\n\n    public static void main(String[] args) {\n        // CHINA or US\n        Locale.setDefault(Locale.CHINA);\n\n        // Load the business framework of each gameLogicServer\n        // cn: 加载游戏逻辑服的业务框架\n        yourListLogic().forEach(BrokerClientStartup::createBarSkeleton);\n\n        /*\n         * Generate actions, broadcasts, and error codes.\n         * cn: 生成 action、广播、错误码\n         */\n        \n        // ----- About generating GDScript code -----\n        generateCodeGDScriptGodot();\n\n        // Added an enumeration error code class to generate error code related information\n        IoGameDocumentHelper.addErrorCodeClass(YourGameCodeEnum.class);\n        // Generate document\n        IoGameDocumentHelper.generateDocument();\n    }\n\n    private static void generateCodeGDScriptGodot() {\n        var documentGenerate = new GDScriptDocumentGenerate();\n        // By default, it will be generated in the target/code directory\n        // cn: 设置代码生成所存放的路径，如果不做任何设置，将会生成在 target/code 目录中\n        String path = rootPath + \"ioGameSdkGDScriptExampleGodot/gen/code\";\n        documentGenerate.setPath(path);\n\n        IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n    }\n}\n```\n\n\n\n\n\n\n### 2025-04-30 - v21.26\n\nhttps://github.com/iohao/ioGame/releases/tag/21.26\n\n**Version update summary**\n\n> 1. refactor(Code generation): Code generation supports importing multiple .proto files\n> 2. refactor(i18n): #376\n\n------\n\n\nSupports importing multiple .proto files\n\n```java\npublic interface SdkProtoFile {\n    String fileName = \"common.proto\";\n    String filePackage = \"common\";\n\n    String fileName2 = \"common2.proto\";\n    String filePackage2 = \"common2\";\n}\n\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\n@ProtoFileMerge(fileName = SdkProtoFile.fileName, filePackage = SdkProtoFile.filePackage)\npublic final class LoginVerifyMessage {\n    /** jwt */\n    String jwt;\n}\n\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\n@ProtoFileMerge(fileName = SdkProtoFile.fileName2, filePackage = SdkProtoFile.filePackage2)\npublic final class BulletMessage {\n    /** id */\n    int bulletId;\n    /** bullet name */\n    String name;\n}\n```\n\n\n**[other updates]**\n\n```xml\n<netty.version>4.1.121.Final</netty.version>\n```\n\n \n\n\n\n### 2025-03-20 - v21.25\n\nhttps://github.com/iohao/ioGame/releases/tag/21.25\n\n**Version update summary**\n\n> 1. fix(broker): DefaultWithElementSelector\n> 2. refactor(net): enhance ResponseCollectItemMessage\n> 3. refactor(core): FlowContext add the createResponseMessage method\n\n------\n\n**[other updates]**\n\n```xml\n<netty.version>4.1.119.Final</netty.version>\n```\n\n \n\n### 2025-02-12 - v21.24\n\nhttps://github.com/iohao/ioGame/releases/tag/21.24\n\n**Version update summary**\n\n> 1. refactor(client): Client support boxing and unboxing\n> 2. refactor(core): FlowContextKit add ofFlowContext method\n> 3. refactor(room): room add getPlayerBySeat method\n> 4. fix(core): [#425](https://github.com/iohao/ioGame/issues/425) When there is a method with the same name as the action, the `actionMethodIndex` is not obtained correctly.\n> 5. refactor(core): MethodParser add parseData method\n\n\n------\n\n\n\n**[client]** Client support boxing and unboxing.\n\n```java\n// my client，support：int、long、boolean、String、List\npublic final class MyInputCommandRegion extends AbstractInputCommandRegion {\n    @Override\n    public void initInputCommand() {\n        this.inputCommandCreate.cmd = 1;\n\t\t// Client support boxing and unboxing\n        ofCommand(2).setTitle(\"enterRoom\").setRequestData(() -> {\n            // enterRoom\n            long roomId = 2;\n            return roomId;\n        });\n        \n        // or \n        ofCommand(2).setTitle(\"enterRoom\").setRequestData(() -> {\n           // enterRoom\n           return LongValue.of(2);\n        });\n    }\n}\n\n// my action\n@ActionController(1)\npublic final class MyAction {\n    /**\n     * enterRoom\n     *\n     * @param roomId roomId\n     */\n    @ActionMethod(2)\n    public void enterRoom(long roomId) {\n    }\n}\n```\n\n\n\n------\n\n**[other updates]**\n\n```xml\n<netty.version>4.1.117.Final</netty.version>\n```\n\n------\n\n\n\n\n\n### 2025-01-08 - v21.23\n\nhttps://github.com/iohao/ioGame/releases/tag/21.23\n\n\n**Version update summary**\n\n> 1. perf(room): OperationHandler adds the processVerify method to control whether the process method is executed, and deprecates the verify method, which is replaced by processVerify.\n> 2. perf(core): Optimize BoolValue and reduce the creation of objects\n> 3. perf(net-core): Enhance the invokeModuleMessage and invokeModuleCollectMessage methods of the BrokerClientItem. The return value must not be null, and error information is added.\n> 4. fix(generate-code): action_method_void.txt\n> 5. perf(doc): GameCode Support single parameter construction method\n> 6. perf(kit): [#412](https://github.com/iohao/ioGame/issues/412)\n> 7. perf(room): Player adds isRobot method. Room adds methods to distinguish between real players and robot players.\n> 8. refactor(proto): [#414](https://github.com/iohao/ioGame/issues/414) Enumeration supports custom value\n> 9. perf(room): room add hasSeat、isRealPlayer method\n> 10. refactor(kit): RandomKit add randomLong method\n> 11. refactor(core): Because the FlowContext method name setUserId is ambiguous, the method name is deprecated.\n>\n>     1. Deprecated FlowContext setUserId method; see bindingUserId.\n>     2. Deprecated FlowContext setUserIdAndGetResult method; see bindingUserIdAndGetResult.\n> 12. refactor(core): broadcastMe Added Tip: Please bind UserId before using this method, see FlowContext.bindingUserId.\n> 13. perf(proto): Generate .proto files in parallel\n> 14. refactor(proto): ProtoGenerateFile supports adding multiple proto packages\n> 15. refactor(kit): TaskKit supports setting Timer\n> 16. refactor(room): Deprecated OperationHandler verify, see processVerify\n> 17. refactor(room): Add OperationCode and enhance Operation\n\n\n------\n\n\n\nAbout **[core]** \n\nThe name of the FlowContext setUserId method is ambiguous. This method is deprecated and replaced by the bindingUserId method.\n\n```java\nflowContext.setUserId(userId); // Deprecated\nflowContext.bindingUserId(userId); // now\n```\n\n\n\nAbout **[kit]** \n\nTaskKit supports setting Timer\n\n```java\nTaskKit.setTimer(new HashedWheelTimer(17, TimeUnit.MILLISECONDS));\n```\n\n\n\nAbout **[room]** \n\n1. Add hasSeat method to room to check whether there are any empty seats in the room.\n2. Room add isRealPlayer method.\n3. Add isRobot method to Player, and add method to distinguish real players from robot players to Room.\n4. Add OperationCode and enhance Operation\n5. Add processVerify method to OperationHandler to control whether to execute process method, and deprecate verify method, replaced by processVerify.\n\nWhen processVerify returns false, process method will not be executed. There is an assertion mechanism in processVerify, and when there is no seat, an error code will be sent to the client to prompt the player.\n\n```java\npublic final class EnterRoomOperationHandler implements OperationHandler {\n    @Override\n    public boolean processVerify(PlayerOperationContext context) {\n        \n        // assert room spaceSize\n        Room room = context.getRoom();\n        GameCode.roomSpaceSizeNotEnough.assertTrue(room.hasSeat());\n\n        long userId = context.getUserId();\n        long score = AccountKit.getScore(userId);\n        \n        return score > 500;\n    }\n\n    @Override\n    public void process(PlayerOperationContext context) {\n        Room room = context.getRoom();\n        ... enterRoom\n    }\n}\n```\n\n\n\nExample of combining OperationCode with enumeration\n\n```java\n@ProtobufClass\n@ProtoFileMerge(fileName = FileMerge.fileName, filePackage = FileMerge.filePackage)\npublic enum MyOperation implements OperationCode {\n    /** quitRoom */\n    quitRoom,\n    /** inRoom */\n    inRoom\n    ;\n\n    final int operationCode;\n\n    FairOperation() {\n        this.operationCode = OperationCode.getAndIncrementCode();\n    }\n\n    @Override\n    public int getOperationCode() {\n        return operationCode;\n    }\n}\n\n// config\npublic void configOperation() {\n    RoomService roomService = ...\n    OperationFactory factory = roomService.getOperationFactory();\n\n    // mappingUser operation\n    factory.mappingUser(MyOperation.inRoom, new InRoomOperationHandler());\n    factory.mappingUser(MyOperation.quitRoom, new QuitRoomOperationHandler());\n}\n```\n\n\n\nAbout **[proto]** \n\n1. Optimize .proto generation and process file generation in parallel.\n\n2. ProtoGenerateFile supports adding multiple proto packages\n\nSupports adding multiple proto packages to better support modularization\n\n```java\n    private static void generateProtoFile() {\n\n        String generateFolder = \"/Users/join/gitme/game/MyGames/proto\";\n        List<String> protoPackageList = List.of(\"com.iohao.happy.robot\"\n                                                ,\"com.iohao.happy.email\");\n\n        var protoGenerateFile = new ProtoGenerateFile()\n                // Generate the directory where the .proto file is stored\n                .setGenerateFolder(generateFolder)\n                // The package name to be scanned\n                .addProtoPackage(protoPackageList)\n                .addProtoPackage(\"com.iohao.happy.common.provide.proto\");\n\n        // generate .proto \n        protoGenerateFile.generate();\n    }\n```\n\n\n\n3. Enumeration supports custom values, java code and generated .proto\n\n```java\n@ProtobufClass\n@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)\npublic enum AnimalTypeEnum implements EnumReadable {\n    /** the cat */\n    cat(0),\n    /** the tiger */\n    tiger(10),\n    ;\n\n    final int value;\n\n    AnimalTypeEnum(int value) {\n        this.value = value;\n    }\n\n    @Override\n    public int value() {\n        return this.value;\n    }\n}\n```\n\n.proto\n\n```protobuf\n// TestAnimalTypeEnum\nenum AnimalTypeEnum {\n  // the cat\n  cat = 0;\n  // the tiger\n  tiger = 10;\n}\n```\n\n\n------\n\n**[other updates]**\n\n```xml\n<netty.version>4.1.116.Final</netty.version>\n```\n\n------\n\n\n\n### 2024-12-02 - v21.22\n\nhttps://github.com/iohao/ioGame/releases/tag/21.22\n\n**Version update summary**\n\n> 1. perf(core): DefaultActionMethodParamParser\n> 1. fix(kit): #407 ClassRefInfoKit invokeSetter\n> 1. #376 i18n DefaultUserHook\n> 1. feat(GenerateCode): #329 Added TypeScript code generation TypeScriptDocumentGenerate, which can generate interactive code for CocosCreator、Vue、Angular.\n\n\n------\n\n\n\n**feat(GenerateCode)**: #329 Added TypeScript code generation TypeScriptDocumentGenerate, which can generate interactive code for CocosCreator、Vue、Angular.\n\n\n\nAbout examples\n\n1. ioGameServerExample: https://github.com/iohao/ioGameExamples/tree/main/SdkExample\n2. CocosCreatorExample: https://github.com/iohao/ioGameSdkTsExampleCocos\n3. VueExample: https://github.com/iohao/ioGameSdkTsExampleVue\n4. HtmlExample: https://github.com/iohao/ioGameSdkTsExampleHtml\n5. AngularExample: https://github.com/iohao/ioGameSdkTsExampleAngular\n\n```java\npublic final class GenerateTest {\n    // setting root path\n    static String rootPath = \"/Users/join/gitme/ioGame-sdk/\";\n\n    public static void main(String[] args) {\n        // CHINA or US\n        Locale.setDefault(Locale.CHINA);\n\n        // Load the business framework of each gameLogicServer\n        // 加载游戏逻辑服的业务框架\n        yourListLogic().forEach(BrokerClientStartup::createBarSkeleton);\n\n        /*\n         * Generate actions, broadcasts, and error codes.\n         * cn: 生成 action、广播、错误码\n         */\n        \n         // About generating TypeScript code\n//        generateCodeVue();\n//        generateCodeAngular();\n//        generateCodeHtml();\n        generateCocosCreator();\n\n        // Added an enumeration error code class to generate error code related information\n        IoGameDocumentHelper.addErrorCodeClass(YourGameCodeEnum.class);\n        // Generate document\n        IoGameDocumentHelper.generateDocument();\n    }\n\n    private static void generateCodeVue() {\n        var documentGenerate = new TypeScriptDocumentGenerate();\n\n        // 设置代码生成所存放的路径，如果不做任何设置，将会生成在 target/code 目录中\n        // By default, it will be generated in the target/code directory\n        String path = rootPath + \"ioGameSdkTsExampleVue/src/assets/gen/code\";\n        documentGenerate.setPath(path);\n\n        // Your .proto path: Set the import path of common_pb in Vue.\n        documentGenerate.setProtoImportPath(\"../common_pb\");\n\n        IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n    }\n\n    private static void generateCodeHtml() {\n        var documentGenerate = new TypeScriptDocumentGenerate();\n\n        // 设置代码生成所存放的路径，如果不做任何设置，将会生成在 target/code 目录中\n        // By default, it will be generated in the target/code directory\n        String path = rootPath + \"ioGameSdkTsExampleHtml/src/assets/gen/code\";\n        documentGenerate.setPath(path);\n\n        // Your .proto path: Set the import path of common_pb in Vue.\n        documentGenerate.setProtoImportPath(\"../common_pb\");\n\n        IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n    }\n\n    private static void generateCocosCreator() {\n        var documentGenerate = new TypeScriptDocumentGenerate();\n\n        // 设置代码生成所存放的路径，如果不做任何设置，将会生成在 target/code 目录中\n        // By default, it will be generated in the target/code directory\n        String path = rootPath + \"ioGameSdkTsExampleCocos/assets/scripts/gen/code\";\n        documentGenerate.setPath(path);\n\n        // Your .proto path: Set the import path of common_pb in CocosCreator\n        documentGenerate.setProtoImportPath(\"db://assets/scripts/gen/common_pb\");\n\n        IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n    }\n\n    private static void generateCodeAngular() {\n        var documentGenerate = new TypeScriptDocumentGenerate();\n\n        // 设置代码生成所存放的路径，如果不做任何设置，将会生成在 target/code 目录中\n        // By default, it will be generated in the target/code directory\n        String path = rootPath + \"ioGameSdkTsExampleAngular/src/assets/gen/code\";\n        documentGenerate.setPath(path);\n\n        // Your .proto path: Set the import path of common_pb in Vue.\n        documentGenerate.setProtoImportPath(\"../common_pb\");\n\n        IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n    }\n}\n```\n\n\n\nAdvantages of SDK Code Generation\n1. Helps client-side developers reduce significant workload by eliminating the need to write a large amount of template code.\n2. Clear and semantically precise. The generated interaction code clearly defines parameter types and return types.\n3. Ensures parameter type safety and clarity in interface methods, effectively avoiding security risks and reducing basic errors during integration.\n4. Reduces communication costs between the server and client during integration; the code serves as documentation. The generated integration code includes documentation and usage examples, and the examples on the methods will guide you on how to use them, making it zero-learning-cost even for beginners.\n5. Helps client-side developers abstract away the interaction with the server, allowing them to focus more on the core business logic.\n6. Reduces the cognitive load during integration. The code is simple to use, similar to local method calls.\n7. Abandons the traditional protocol-based approach in favor of an interface-method-based integration approach.\n\n------\n\n\n\n\n### 2024-11-15 - v21.20\n\nhttps://github.com/iohao/ioGame/releases/tag/21.20\n\n**Version update summary**\n> 1. feat(GenerateDoc): Add DocumentMethod annotation : Action supports generating documentation method names through annotations.\n> 1. BroadcastDebug enhancements.\n> 1. feat(GenerateCode): #328 Added C# code generation CsharpDocumentGenerate, which can generate interactive code for Unity and Godot.\n\n\n------\n\n**feat(GenerateDoc):** Add DocumentMethod annotation : Action supports generating documentation method names through annotations.\n\nBy default, the method names in the generated action interaction code use the method names from the Java action. The action can add the `DocumentMethod` annotation to fix the method name, and when generating the integration code, ioGame will prioritize using the value of the `DocumentMethod` annotation.\n\n```java\n@ActionController(SdkCmd.cmd)\npublic final class SdkAction {    \n    @ActionMethod(SdkCmd.noReturn)\n    @DocumentMethod(\"noReturnMethod\")\n    public void noReturn(String name) {\n        ... ...\n    }\n}\n```\n\n---\n\n**feat(GenerateCode):** #328 Added C# code generation CsharpDocumentGenerate, which can generate interactive code for Unity and Godot.\n\n\n\nAbout examples\n\n1. see https://github.com/iohao/ioGameExamples/tree/main/SdkExample\n2. UnityExample: https://github.com/iohao/ioGameSdkCsharpExampleUnity\n3. GodotExample: https://github.com/iohao/ioGameSdkCsharpExampleGodot\n\n```java\npublic final class GenerateTest {\n    // setting root path\n    static String rootPath = \"/Users/join/gitme/ioGame-sdk/\";\n\n    public static void main(String[] args) {\n        // CHINA or US\n        Locale.setDefault(Locale.CHINA);\n\n        // Load the business framework of each gameLogicServer\n        // 加载游戏逻辑服的业务框架\n        yourListLogic().forEach(BrokerClientStartup::createBarSkeleton);\n\n        /*\n         * Generate actions, broadcasts, and error codes.\n         * cn: 生成 action、广播、错误码\n         */\n        // About generating C# code\n        generateCodeCsharpGodot();\n        generateCodeCsharpUnity();\n\n        // Added an enumeration error code class to generate error code related information\n        IoGameDocumentHelper.addErrorCodeClass(YourGameCodeEnum.class);\n        // Generate document\n        IoGameDocumentHelper.generateDocument();\n    }\n\n    private static void generateCodeCsharpUnity() {\n        var documentGenerate = new CsharpDocumentGenerate();\n        // 设置代码生成所存放的路径，如果不做任何设置，将会生成在 target/code 目录中\n        // By default, it will be generated in the target/code directory\n        String path = rootPath + \"ioGameSdkCsharpExampleUnity/Assets/Scripts/Gen/Code\";\n        documentGenerate.setPath(path);\n\n        IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n    }\n\n    private static void generateCodeCsharpGodot() {\n        var documentGenerate = new CsharpDocumentGenerate();\n        // 设置代码生成所存放的路径，如果不做任何设置，将会生成在 target/code 目录中\n        // By default, it will be generated in the target/code directory\n        String path = rootPath + \"ioGameSdkCsharpExampleGodot/script/gen/code\";\n        documentGenerate.setPath(path);\n\n        IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n    }\n}\n```\n\n\n\nAdvantages of SDK Code Generation\n1. Helps client-side developers reduce significant workload by eliminating the need to write a large amount of template code.\n2. Clear and semantically precise. The generated interaction code clearly defines parameter types and return types.\n3. Ensures parameter type safety and clarity in interface methods, effectively avoiding security risks and reducing basic errors during integration.\n4. Reduces communication costs between the server and client during integration; the code serves as documentation. The generated integration code includes documentation and usage examples, and the examples on the methods will guide you on how to use them, making it zero-learning-cost even for beginners.\n5. Helps client-side developers abstract away the interaction with the server, allowing them to focus more on the core business logic.\n6. Reduces the cognitive load during integration. The code is simple to use, similar to local method calls.\n7. Abandons the traditional protocol-based approach in favor of an interface-method-based integration approach.\n\n------\n\n**[other updates]**\n\n```xml\n<protobuf-java.version>3.25.5</protobuf-java.version>\n```\n\n---\n\n\n\n### 2024-10-28 - v21.19\n\nhttps://github.com/iohao/ioGame/releases/tag/21.19\n\n\n\n**版本更新汇总**\n\n> 1. [core] FlowContext provides the setUserId method to simplify the login operation.\n> 2. [broker] Added RingElementSelector load balancing implementation and set it as default to replace RandomElementSelector\n> 3. [core] [#386](https://github.com/iohao/ioGame/issues/386) Action supports constructor injection with parameters in Spring\n> 4. Simplify the implementation class of ActionParserListener related to ProtoDataCodec. and #386\n> 5. perf(i18n): 🐳 [#376](https://github.com/iohao/ioGame/issues/376) cmd check tips\n> 6. refactor(external): simplify and improve externalCache\n\n\n------\n\n**[core]** FlowContext provides the setUserId method to simplify the login operation.\n> FlowContext 提供登录方法以简化登录的使用\n\n```java\n@ActionController(LoginCmd.cmd)\npublic class TheLoginAction {\n    ... ...\n\t@ActionMethod(LoginCmd.login)\n    public UserInfo loginVerify(LoginVerify loginVerify, FlowContext flowContext) {\n        long userId = ...;\n        \n        // Deprecated\n\t\tboolean success = UserIdSettingKit.settingUserId(flowContext, userId);\n        // now\n        boolean success = flowContext.setUserId(userId);\n\n        return ...;\n    }\n}\n```\n\n---\n\n**[core]** [#386](https://github.com/iohao/ioGame/issues/386) Action supports constructor injection with parameters in Spring\n> 在 Spring 中，Action 支持构造函数注入\n\n```java\n// Action supports constructor injection in Spring.\n@Component\n@AllArgsConstructor\n@ActionController(PersonCmd.cmd)\npublic class PersonAction {    \n    final PersonService personService;\n    ...\n}\n```\n\n---\n\nrefactor(external): simplify and improve externalCache\n> 简化与提升游戏对外服缓存\n\n```java\n// create externalCache\nprivate static void extractedExternalCache() {\n    // Deprecated\n    DefaultExternalCmdCache externalCmdCache = new DefaultExternalCmdCache();\n    // now\n    var externalCmdCache = ExternalCmdCache.of();\n}\n```\n\n------\n\n**[其他更新]**\n\n```xml\n<netty.version>4.1.114.Final</netty.version>\n```\n\n------\n\n### 2024-10-09 - v21.18\n\nhttps://github.com/iohao/ioGame/releases/tag/21.18\n\n\n\n**版本更新汇总**\n\n> - [external] [#375](https://github.com/iohao/ioGame/issues/375) Support for lightweight or embedded Linux distributions. 支持轻量级或嵌入式 Linux 发行版。\n> - [core] [#376](https://github.com/iohao/ioGame/issues/376) Support i18n, such as logs and internal messages. 框架内的日志、内部消息支持 i18n。\n\n\n------\n\n**[core]**\n\n [#376](https://github.com/iohao/ioGame/issues/376) Support i18n, such as logs and internal messages. 框架内的日志、内部消息支持 i18n。\n\n```java\npublic class DemoApplication {\n    public static void main(String[] args) {\n        // setting defaultLocale, such as US or CHINA\n        Locale.setDefault(Locale.US);\n        Locale.setDefault(Locale.CHINA);\n\n        ... start ioGame\n    }\n}\n```\n\n------\n\n**[其他更新]**\n\n```\n<scalecube.version>2.6.17</scalecube.version>\n```\n\n------\n\n\n\n### 2024-09-25 - v21.17\n\nhttps://github.com/iohao/ioGame/releases/tag/21.17\n\n**版本更新汇总**\n\n> - [core] 简化 TraceIdSupplier 全链路调用日志跟踪默认实现\n> - [core] FlowContext 提供用户（玩家）所关联的用户线程执行器信息及虚拟线程执行器信息方法\n\n---\n\n**[core]**\n\nFlowContext 提供用户（玩家）所关联的用户线程执行器信息及虚拟线程执行器信息方法\n\n```java\nvoid testThreadExecutor(FlowContext flowContext) {\n    // 获取 - 用户（玩家）所关联的用户线程执行器信息及虚拟线程执行器信息\n\n    // 用户虚拟线程执行器信息\n    ThreadExecutor virtualThreadExecutor = flowContext.getVirtualThreadExecutor();\n    // 用户线程执行器信息\n    ThreadExecutor threadExecutor = flowContext.getThreadExecutor();\n\n    threadExecutor.execute(() -> {\n        log.info(\"execute\");\n    });\n\n    threadExecutor.executeTry(() -> {\n        log.info(\"executeTry\");\n    });\n\n    // get Executor\n    Executor executor = threadExecutor.executor();\n}\n```\n\n------\n\n\n\n### 2024-09-09 - v21.16\n\nhttps://github.com/iohao/ioGame/releases/tag/21.16\n\n**版本更新汇总**\n\n> - [kit] [#291](https://github.com/iohao/ioGame/issues/291) 增加轻量可控的延时任务\n> - [kit] 细分时间日期相关工具。\n> - [Archive] [#363](https://github.com/iohao/ioGame/issues/363)  light-redis-lock 相关模块\n> - [Archive] [#364](https://github.com/iohao/ioGame/issues/364) light-timer-task 相关模块\n> - [core] 增加同一个 ActionController 相同的 action 方法名只允许存在一个的检测。\n> - [core] Banner 增加启动时的错误数量提示。\n> - [core] [#365](https://github.com/iohao/ioGame/issues/365) 支持对接文档生成时，可以根据路由访问权限来控制文档的生成\n\n------\n\n**[kit]**\n\n[#291](https://github.com/iohao/ioGame/issues/291) 增加轻量可控的延时任务\n\n\n for example \n\n```java\n@Test\npublic void example() {\n    long timeMillis = System.currentTimeMillis();\n\n    DelayTask delayTask = DelayTaskKit.of(() -> {\n                long value = System.currentTimeMillis() - timeMillis;\n                log.info(\"1 - 最终 {} ms 后，执行延时任务\", value);\n            })\n            .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时  \n            .task(); // 启动任务\n\n    delayTask.plusTimeMillis(500); // 增加 0.5 秒的延时\n    delayTask.minusTimeMillis(500);// 减少 0.5 秒的延时时间\n\n    // 因为 taskId 相同，所以会覆盖之前的延时任务\n    String taskId = delayTask.getTaskId();\n    delayTask = DelayTaskKit.of(taskId, () -> {\n                long value = System.currentTimeMillis() - timeMillis;\n                log.info(\"2 - 最终 {} ms 后，执行延时任务\", value);\n            })\n            .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时\n            .task(); // 启动任务\n\n    // 取消延时任务，下面两个方法是等价的\n    delayTask.cancel();\n    DelayTaskKit.cancel(taskId);\n\n    // 可以通过 taskId 查找该延时任务\n    Optional<DelayTask> optionalDelayTask = DelayTaskKit.optional(taskId);\n    if (optionalDelayTask.isPresent()) {\n        var delayTask = optionalDelayTask.get();\n    }\n\n    // 通过 taskId 查找延时任务，存在则执行给定逻辑\n    DelayTaskKit.ifPresent(taskId, delayTask -> {\n        delayTask.plusTimeMillis(500); // 增加 0.5 秒的延时时间\n    });\n}\n```\n\n------\n\n细分时间日期相关工具。\n\nsee com.iohao.game.common.kit.time\n\n------\n\n**[Archive]**\n\n[#363](https://github.com/iohao/ioGame/issues/363)  light-redis-lock 相关模块\n\n将 light-redis-lock、light-redis-lock-spring-boot-starter 模块做归档。在过去的时间里，由于一直没有改动这些模块的相关内容，现决定将不再上传到 maven 库中，以节约公共资源。如果你使用了该模块的相关内容，请指定最后一个版本即可。如\n\n```xml\n<!-- https://mvnrepository.com/artifact/com.iohao.game/light-redis-lock -->\n<dependency>\n    <groupId>com.iohao.game</groupId>\n    <artifactId>light-redis-lock</artifactId>\n    <version>21.15</version>\n</dependency>\n\n<!-- https://mvnrepository.com/artifact/com.iohao.game/light-redis-lock-spring-boot-starter -->\n<dependency>\n    <groupId>com.iohao.game</groupId>\n    <artifactId>light-redis-lock-spring-boot-starter</artifactId>\n    <version>21.15</version>\n</dependency>\n```\n\n\n\n\n------\n\n[#364](https://github.com/iohao/ioGame/issues/364) light-timer-task 相关模块\n\n将 light-timer-task 模块做归档。在过去的时间里，由于一直没有改动这些模块的相关内容；同时，也因为框架内置了类似的功能 #291 。现决定将不再上传到 maven 库中，以节约公共资源。如果你使用了该模块的相关内容，请指定最后一个版本即可。如\n\n```xml\n<!-- https://mvnrepository.com/artifact/com.iohao.game/light-timer-task -->\n<dependency>\n    <groupId>com.iohao.game</groupId>\n    <artifactId>light-timer-task</artifactId>\n    <version>21.15</version>\n</dependency>\n```\n\n\n\n------\n\n**[core]**\n\n[#365](https://github.com/iohao/ioGame/issues/365) 支持对接文档生成时，可以根据路由访问权限来控制文档的生成\n\n\n\n生成相关代码的使用及相关文档\n\n- `ExternalGlobalConfig.accessAuthenticationHook`，相关文档路由访问权限控制\n- IoGameDocumentHelper，相关文档游戏对接文档生成\n\nfor example \n\n```java\npublic class MyExternalServer {\n    public static void extractedAccess() {\n        // https://iohao.github.io/game/docs/external/access_authentication\n        var accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;\n        ... 省略部分代码\n        // 添加 - 拒绝玩家访问权限的控制\n        accessAuthenticationHook.addRejectionCmd(RankCmd.cmd, RankCmd.internalUpdate);\n    }\n}\n\npublic class TestGenerate {\n    ... 省略部分代码\n    public static void main(String[] args) {\n        // 对外服访问权限控制\n        MyExternalServer.extractedAccess();\n        // （复用）设置文档路由访问权限控制\n      IoGameDocumentHelper.setDocumentAccessAuthentication(ExternalGlobalConfig.accessAuthenticationHook::reject);\n        \n        // ====== 生成对接文档、生成 proto ======\n//        generateCsharp();\n//        generateTypeScript();\n        // 生成文档\n        IoGameDocumentHelper.generateDocument();\n        // .proto 文件生成\n//        generateProtoFile();\n    }\n}\n```\n\n\n\n**预览 - 没有做控制前的生成**\n\n```latex\n==================== RankAction  ====================\n路由: 4 - 1  --- 【listRank】 --- 【RankAction:48】【listRank】\n    方法参数: StringValue 排行类型\n    方法返回值: ByteValueList<RankUpdate> 玩家排行名次更新\n \n路由: 4 - 10  --- 【玩家排行名次更新】 --- 【RankAction:60】【internalUpdate】\n    方法参数: RankUpdate 玩家排行名次更新\n    方法返回值: void \n```\n\n**预览 - 加入了访问控制后的生成**\n\n我们可以看见，路由为 4-10 的 action 方法没有生成到对接文档中。\n\n```latex\n==================== RankAction  ====================\n路由: 4 - 1  --- 【listRank】 --- 【RankAction:48】【listRank】\n    方法参数: StringValue 排行类型\n    方法返回值: ByteValueList<RankUpdate> 玩家排行名次更新\n```\n\n\n\n提示：除了文档文档的访问权限控制外，还支持 SDK TypeScript、SDK C# ...等客户端代码生成的访问权限控制。\n\n\n\n\n------\n\n**[其他更新]**\n\n```xml\n<netty.version>4.1.113.Final</netty.version>\n```\n\n------\n\n\n\n### 2024-08-26 - v21.15\n\nhttps://github.com/iohao/ioGame/releases/tag/21.15\n\n\n\n**版本更新汇总**\n\n> - [core] [#351](https://github.com/iohao/ioGame/issues/351)  增加 UserProcessor 线程执行器的选择策略扩展\n> - [core] [#350](https://github.com/iohao/ioGame/issues/350) 修复请求消息在 Broker 环节乱序的问题\n> - [core] [#353](https://github.com/iohao/ioGame/issues/353) 对接文档支持框架内置错误码的生成\n> - [core] [#354](https://github.com/iohao/ioGame/issues/354) 日志打印调整\n> - [core] [#359](https://github.com/iohao/ioGame/issues/359) [逻辑服-监听] 增加打印其他进程逻辑服的上线与下线信息\n> - [core] 优化 ThreadExecutorRegion 相关实现类。\n> - [external] UserSession 接口新增 ofRequestMessage 方法，简化玩家在游戏对外服中创建请求对象。\n\n------\n\n**[external]**\n\nUserSession 接口新增 ofRequestMessage 方法，简化玩家在游戏对外服中创建请求对象。 for example\n\n```java\nvar cmdInfo = CmdInfo.of(1, 1);\nRequestMessage request = userSession.ofRequestMessage(cmdInfo);\n```\n\n------\n\n**[core]**\n\n[#359](https://github.com/iohao/ioGame/issues/359) [逻辑服-监听] 增加打印其他进程逻辑服的上线与下线信息\n\n```java\npublic class MyLogicServer extends AbstractBrokerClientStartup {\n    ...\n\n    @Override\n    public BrokerClientBuilder createBrokerClientBuilder() {\n        BrokerClientBuilder builder = BrokerClient.newBuilder();\n        ...\n        // 添加监听 - 打印其他进程逻辑服的上线与下线信息\n        builder.addListener(SimplePrintBrokerClientListener.me());\n        return builder;\n    }\n}\n```\n\n------\n\n[#351](https://github.com/iohao/ioGame/issues/351) 增加 UserProcessor 线程执行器的选择策略扩展\n\n> for example，注意事项：当你的 UserProcessor 做了线程执行器的选择策略扩展，需要重写 CustomSerializer 接口的相关方法。\n\n```java\n// 为请求消息开启有序的、多线程处理的优化\nIoGameGlobalConfig.enableUserProcessorExecutorSelector();\n```\n\n\n\n------\n\n### 2024-08-08 - v21.14\n\nhttps://github.com/iohao/ioGame/releases/tag/21.14\n\n\n\n**版本更新汇总**\n\n> - [code quality] 提升代码质量，see [ioGame - Qodana Cloud](https://qodana.cloud/organizations/3k6Pm/teams/zxRGm)\n> - [javadoc] 增强相关模块的 javadoc ：业务框架、压测与模拟客户端请求、领域事件、Room\n> - [core]  [#346](https://github.com/iohao/ioGame/issues/346) 业务框架 InOutManager 提供扩展点\n> - [core]  [#344](https://github.com/iohao/ioGame/issues/344) 登录时，如果 FlowContext 存在 userId 就不请求游戏对外服\n> - [broker] fixed [#342](https://github.com/iohao/ioGame/issues/342)  非集群环境下，Broker 断开重启后，逻辑服没有将其重新加入到 BrokerClientManager 中所引发的 NPE。\n\n------\n\n**[core]**\n\n [#346](https://github.com/iohao/ioGame/issues/346) **业务框架 InOutManager 提供扩展点**\n\n在构建器中配置 InOutManager 策略，框架内置了两个实现类，分别是\n\n1. ofAbcAbc ：in ABC，out ABC 的顺序，即编排时的顺序。\n2. ofPipeline：in ABC，out CBA 的顺序，类似的 netty Pipeline 。（默认策略，如果不做任何设置，将使用该策略）\n\n\n\nfor example 在构建器中配置 InOutManager 策略\n\n```java\npublic void config() {\n    BarSkeletonBuilder builder = ...;\n    builder.setInOutManager(InOutManager.ofAbcAbc());\n    builder.setInOutManager(InOutManager.ofPipeline());\n}\n```\n\n\n\n------\n\n\n\n### 2024-07-24 - v21.13\n\nhttps://github.com/iohao/ioGame/releases/tag/21.13\n\n\n\n**版本更新汇总**\n\n- [external]  [#334](https://github.com/iohao/ioGame/issues/334) 顶号操作 bug，有概率触发并发问题\n- [core]  FlowContext 新增 createRequestCollectExternalMessage 方法\n- [javadoc] 源码 javadoc 增强\n\n------\n\n\n\n**[core]**\n\nFlowContext 新增 createRequestCollectExternalMessage 方法，request 与游戏对外服交互。\n\n```java\n... ... 省略部分代码\n@ActionMethod(ExternalBizRegionCmd.listOnlineUserAll)\npublic List<Long> listOnlineUserAll(FlowContext flowContext) {\n\n    // 创建 RequestCollectExternalMessage\n    var request = flowContext\n            .createRequestCollectExternalMessage(MyExternalBizCode.onlineUser);\n\n    // 访问多个【游戏对外服】\n    var collectExternalMessage = flowContext\n            .invokeExternalModuleCollectMessage(request);\n\n    return listUserId(collectExternalMessage);\n}\n```\n\n------\n\n\n\n**[其他更新]**\n\n```xml\n<netty.version>4.1.112.Final</netty.version>\n<lombok.version>1.18.34</lombok.version>\n```\n\n------\n\n\n\n### 2024-07-08 - v21.12\n\nhttps://github.com/iohao/ioGame/releases/tag/21.12\n\n\n\n**版本更新汇总**\n\n- [light-game-room]  [#326](https://github.com/iohao/ioGame/issues/326) GameFlowContext getRoom、getPlayer 方法返回值改成泛型\n- [对接文档]  [#330](https://github.com/iohao/ioGame/issues/330) 增强，支持对接文档生成与扩展，包括文本文档生成、联调代码生成 ...等\n\n\n\n当前版本，为之后生成联调代码做了充分的准备\n\n------\n\n\n\n**[light-game-room]**\n\n[#326](https://github.com/iohao/ioGame/issues/326) GameFlowContext getRoom、getPlayer 方法返回值改成泛型\n\n```java\nGameFlowContext gameFlowContext = ...;\n// FightRoomEntity 是自定义的 Room 对象\n\n// Room、Player 在使用时，不需要强制转换了\nFightRoomEntity room =  gameFlowContext.getRoom();\nFightPlayerEntity player = gameFlowContext.getPlayer();\n```\n\n------\n\n\n\n**[对接文档]** \n\n[#330](https://github.com/iohao/ioGame/issues/330) 增强，支持对接文档生成与扩展，包括文本文档生成、联调代码生成 ...等。开发者做更多个性化的扩展\n\n\n\n在该版本中，我们已经新做了对接文档相关模块；该版本功能更加的强大，使用上也更加的简洁。新版本的对接文档模块，除了能提供文本文档的生成外，还能支持生成与客户端联调的代码、并且是可扩展的。通常，客户端联调代码有：\n\n1. 支持生成 C# 客户端的联调代码，通常用在 Unity、Godot 客户端\n2. 支持生成 TypeScript 客户端的联调代码，通常用在 cocos、laya 客户端\n\n\n\n```java\npublic static void main(String[] args) {\n    // 添加枚举错误码 class，用于生成错误码相关信息\n    IoGameDocumentHelper.addErrorCodeClass(GameCode.class);\n\n    // 添加文档生成器，文本文档\n    IoGameDocumentHelper.addDocumentGenerate(new TextDocumentGenerate());\n    // 添加文档生成器，Ts 联调代码生成\n    IoGameDocumentHelper.addDocumentGenerate(new TypeScriptDocumentGenerate());\n    // 生成文档\n    IoGameDocumentHelper.generateDocument();\n}\n```\n\n上述代码\n\n- 添加了错误码的生成\n- 添加了文本文档的生成\n- 添加了 Ts 客户端联调代码的生成（包括 action、广播、错误码...相关代码的生成）， [SDK TypeScript 客户端代码生成；方便 CocosCeator、或其他支持 TypeScript 的客户端对接。 #329](https://github.com/iohao/ioGame/issues/329)\n\n\n\naddDocumentGenerate 是可扩展的，这将意味着开发者可以扩展出 C#、GodotScript、Js ...等不同客户端的联调代码。默认，我们提供了一个文本文档，即 TextDocumentGenerate，如果默认的实现满足不了当下需求，开发者也可以定制个性化的文档，如 json 格式的。\n\n\n\n\n\n\n**新增 DocumentGenerate 接口**\n\n开发者可利用该接口进行定制个性化的对接文档，如代码生成 ...等。\n\n```java\n/**\n * 对接文档生成接口，可扩展不同的实现\n */\npublic interface DocumentGenerate {\n    /**\n     * 生成文档\n     *\n     * @param ioGameDocument ioGameDocument\n     */\n    void generate(IoGameDocument ioGameDocument);\n}\n\n/**\n * 文档相关信息，如 action 相关、广播相关、错误码相关。\n */\n@Getter\npublic final class IoGameDocument {\n    /** 已经解析好的广播文档 */\n    List<BroadcastDocument> broadcastDocumentList;\n    /** 已经解析好的错误码文档 */\n    List<ErrorCodeDocument> errorCodeDocumentList;\n    /** 已经解析好的 action 文档 */\n    List<ActionDoc> actionDocList;\n}\n```\n\n\n\n开发者可以通过实现 DocumentGenerate 接口来扩展不同的文档生成，开发者可以扩展此接口来定制更多个性化的扩展，如\n\n- html 版本的文档。\n- json 版本的文档。\n- 其他语言的联调文档 ...等。\n\n\n\n```java\n// 使用示例\nprivate static void test() {\n    var documentGenerate = new YourDocumentGenerate();\n    IoGameDocumentHelper.addDocumentGenerate(documentGenerate);\n}\n```\n\n\n\n------\n\n其他：废弃旧版本对接文档相关类 DocActionSend、DocActionSends、ActionDocs、ActionSendDoc、ActionSendDocs、ActionSendDocsRegion、BarSkeletonDoc、BroadcastDoc、BroadcastDocBuilder、ErrorCodeDocs、ErrorCodeDocsRegion。\n\n------\n\n21.10 及之前版本的使用示例（对接文档）\n\n```java\npublic static void main(String[] args) {\n    ... 省略部分代码\n\n    new NettyRunOne()\n            ... ...\n            .startup();\n\n    // 生成对接文档\n    BarSkeletonDoc.me().buildDoc();\n}\n```\n\n------\n\n\n\n### 2024-06-21 - v21.10\n\n（问题版本）\n\nhttps://github.com/iohao/ioGame/releases/tag/21.10\n\n------\n\n**版本更新汇总**\n\n- [core] [#315](https://github.com/iohao/ioGame/issues/315) ResponseMessage 增加协议碎片便捷获取，简化跨服调用时的使用\n- [core] ActionCommand 增加 containAnnotation、getAnnotation 方法，简化获取 action 相关注解信息的使用。\n- [kit] [动态属性]  增加 ifNull 方法，如果动态属性值为 null，则执行给定的操作，否则不执行任何操作。执行给定操作后将得到一个返回值，该返回值会设置到动态属性中。\n- [kit]  TimeKit 增加 nowLocalDate 方法，可减少 LocalDate 对象的创建；优化 currentTimeMillis 方法的时间更新策略。同时，优化 nowLocalDate、currentTimeMillis 方法，不使用时将不会占用相关资源。\n- [EventBus]  分布式事件总线增加 EventBusRunner 接口。EventBus 接口化，方便开发者自定义扩展。fix 订阅者使用自身所关联的 EventBus 处理相关事件。\n\n------\n\n**[core]** [315](https://github.com/iohao/ioGame/issues/315) ResponseMessage 增加协议碎片便捷获取，简化跨服调用时的使用\n\n框架具备协议碎片特性。某些业务中，我们需要跨服访问其他游戏逻辑服，以获取某些业务数据；一些简单的数据，我们可以通过协议碎片来返回，从而避免定义过多的协议。\n\n\n\n现为 ResponseMessage 增加协议碎片支持，简化跨服调用时的使用，新增的方法如下\n\n```java\npublic void test() {\n    ResponseMessage responseMessage = ...;\n\n    // object\n    responseMessage.getValue(Student.class);\n    List<Student> listValue = responseMessage.listValue(Student.class);\n\n    // int\n    int intValue = responseMessage.getInt();\n    List<Integer> listInt = responseMessage.listInt();\n\n    // long\n    long longValue = responseMessage.getLong();\n    List<Long> listLong = responseMessage.listLong();\n\n    // String\n    String stringValue = responseMessage.getString();\n    List<String> listString = responseMessage.listString();\n\n    // boolean\n    boolean boolValue = responseMessage.getBoolean();\n    List<Boolean> listBoolean = responseMessage.listBoolean();\n}\n```\n\n\n\n示例说明\n\n- HomeAction 是 【Home 游戏逻辑服】提供的 action\n- UserAction 是 【User 游戏逻辑服】提供的 action\n\n两个逻辑服的交互如下，UserAction 使用跨服方式调用了【Home 游戏逻辑服】的几个方法，并通过 responseMessage 的协议碎片支持，简化跨服调用时的使用。\n\n\n\n示例中演示了 string、string list、object list 的简化使用（协议碎片获取时的简化使用）。\n\n```java\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class Student {\n    String name;\n}\n\n// home 游戏逻辑服提供的 action\npublic class HomeAction {\n    @ActionMethod(HomeCmd.name)\n    public String name() {\n        return \"a\";\n    }\n\n    @ActionMethod(HomeCmd.listName)\n    public List<String> listName() {\n        return List.of(\"a\", \"b\");\n    }\n\n    @ActionMethod(HomeCmd.listStudent)\n    public List<Student> listStudent() {\n        Student student = new Student();\n        student.name = \"a\";\n\n        Student student2 = new Student();\n        student2.name = \"b\";\n\n        return List.of(student, student2);\n    }\n}\n\n@ActionController(UserCmd.cmd)\npublic class UserAction {\n    @ActionMethod(UserCmd.userSleep)\n    public void userSleep(FlowContext flowContext) {\n\n        flowContext.invokeModuleMessageAsync(HomeCmd.of(HomeCmd.name), responseMessage -> {\n            String name = responseMessage.getString();\n            log.info(\"{}\", name);\n        });\n\n        flowContext.invokeModuleMessageAsync(HomeCmd.of(HomeCmd.listName), responseMessage -> {\n            var listName = responseMessage.listString();\n            log.info(\"{}\", listName);\n        });\n\n        flowContext.invokeModuleMessageAsync(HomeCmd.of(HomeCmd.listStudent), responseMessage -> {\n            List<Student> studentList = responseMessage.listValue(Student.class);\n            log.info(\"{}\", studentList);\n        });\n    }\n}\n```\n\n\n\n------\n\n**[core]** ActionCommand 增加 containAnnotation、getAnnotation 方法，简化获取 action 相关注解信息的使用。\n\n```java\nActionCommand actionCommand = flowContext.getActionCommand();\n\nbool contain = actionCommand.containAnnotation(DisableDebugInout.class);\nvar annotation = actionCommand.getAnnotation(DisableDebugInout.class);\n```\n\n\n\n------\n\n**[EventBus] 分布式事件总线**\n\n1. [增强扩展] 将抽象类 AbstractEventBusRunner 标记为过时的，由接口 EventBusRunner 代替。\n2. [增强扩展] 分布式事件总线 EventBus 接口化，方便开发者自定义扩展。增加[总线相关的 javadoc](https://iohao.github.io/javadoc/com/iohao/game/action/skeleton/eventbus/package-summary.html)。\n3. [fix] 订阅者使用自身所关联的 EventBus 处理相关事件。\n\n\n\n关于 fix 订阅者使用自身所关联的 EventBus 处理相关事件，在此之前可能引发 bug 的场景如下\n\n1. 【游戏逻辑服 A】 发布事件。\n2. 【游戏逻辑服 B】 订阅者接收事件并处理，在处理过程中又调用了【游戏逻辑服 A】 某个 action 方法。\n\n\n\n该业务场景，会在多服单进程下会引发调用超时，但在多服多进程下则不会超时。\n\n\n\n------\n\n**[kit] TimeKit**\n\n增强 TimeKit 增加 nowLocalDate 方法，可减少 LocalDate 对象的创建；\n\n优化 currentTimeMillis 方法的时间更新策略。\n\n优化 nowLocalDate、currentTimeMillis 不使用时将不会占用相关资源。\n\n```csharp\n@Test\npublic void test() {\n    long millis = TimeKit.currentTimeMillis();\n    Assert.assertTrue(millis > 0);\n\n    LocalDate localDate = TimeKit.nowLocalDate();\n    Assert.assertTrue(localDate.isEqual(LocalDate.now()));\n}\n```\n\n\n\n------\n\n**[kit] 动态属性**\n\n[动态属性] 增加 ifNull 方法，如果动态属性值为 null，则执行给定的操作，否则不执行任何操作。执行给定操作后将得到一个返回值，该返回值会设置到动态属性中。\n\n```csharp\npublic class AttrOptionDynamicTest {\n    // 动态属性 key\n    AttrOption<AttrCat> attrCatOption = AttrOption.valueOf(\"AttrCat\");\n\n    @Test\n    public void ifNull() {\n        var myAttrOptions = new MyAttrOptions();\n        Assert.assertNull(myAttrOptions.option(attrCatOption));\n\n        // 如果 catAttrOption 属性为 null，则创建 AttrCat 对象，并赋值到属性中\n        myAttrOptions.ifNull(attrCatOption, AttrCat::new);\n        Assert.assertNotNull(myAttrOptions.option(attrCatOption));\n    }\n\n    private static class AttrCat {\n        String name;\n    }\n\n    @Getter\n    private static class MyAttrOptions implements AttrOptionDynamic {\n        final AttrOptions options = new AttrOptions();\n    }\n}\n```\n\n\n\n------\n\n**[其他 - 相关库升级]**\n\n<netty.version>4.1.111.Final</netty.version>\n\n<jctools-core.version>4.0.5</jctools-core.version>\n\n<jprotobuf.version>2.4.23</jprotobuf.version>\n\n---\n\n\n\n### 2024-06-03 - v21.9\n\n（问题版本）\n\nhttps://github.com/iohao/ioGame/releases/tag/21.9\n\n\n---\n\n**版本更新汇总**\n\n- [core]  [#294](https://github.com/iohao/ioGame/issues/294) 增加范围内的广播接口 RangeBroadcaster，业务参数支持基础类型的简化使用\n- [core-对接文档]  [#293](https://github.com/iohao/ioGame/issues/293) 广播文档构建器支持对参数的单独描述\n- [light-game-room]   [#297](https://github.com/iohao/ioGame/issues/297) 模拟系统创建房间，RoomCreateContext 的使用\n- [light-game-room]   [#298](https://github.com/iohao/ioGame/issues/298) 模拟系统创建房间，GameFlowContext 的使用\n- [core]   [#301](https://github.com/iohao/ioGame/issues/301) FlowContext 更新元信息后，需要立即生效（跨服调用时）\n- [内置 kit] 开放 TaskListener 接口\n- 为 SimpleRoom aggregationContext 属性提供默认值，移除 RoomCreateContext 接口的 getAggregationContext 方法，以免产生误导。\n\n\n---\n\n\n\n**[light-game-room]**\n\n为 SimpleRoom aggregationContext 属性提供默认值\n\n<br>\n\n[#297](https://github.com/iohao/ioGame/issues/297)，模拟系统创建房间，RoomCreateContext 的使用\n\n>  移除 RoomCreateContext 接口的 getAggregationContext 方法，以免产生误导。\n>\n> RoomCreateContext 增加默认重载\n\n```java\nRoomCreateContext.of(); // 无房间创建者，通常表示系统创建\nRoomCreateContext.of(userId); // 房间创建者为 userId\n```\n\n<br>\n\n [#298](https://github.com/iohao/ioGame/issues/298) 模拟系统创建房间，GameFlowContext 的使用\n\n```java\npublic void test() {\n    Room room = ...;\n    GameFlowContext context = GameFlowContext.of(room);\n    ... 省略部分代码\n}\n```\n\n---\n\n**[core]**\n\n[#294](https://github.com/iohao/ioGame/issues/294) 增加范围内的广播接口 RangeBroadcaster，业务参数支持基础类型的简化使用\n\n```java\npublic void testRangeBroadcaster(FlowContext flowContext) {\n    // ------------ object ------------\n    // 广播 object\n    DemoBroadcastMessage message = new DemoBroadcastMessage();\n    message.msg = \"helloBroadcast --- 1\";\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessage(cmdInfo, message);\n    // 广播 object list\n    List<DemoBroadcastMessage> messageList = List.of(message);\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessageList(cmdInfo, messageList);\n\n    // ------------ int ------------\n    // 广播 int\n    int intValue = 1;\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessage(cmdInfo, intValue);\n    // 广播 int list\n    List<Integer> intValueList = List.of(1, 2);\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessageIntList(cmdInfo, intValueList);\n\n    // ------------ long ------------\n    // 广播 long\n    long longValue = 1L;\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessage(cmdInfo, longValue);\n    // 广播 long list\n    List<Long> longValueList = List.of(1L, 2L);\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessageLongList(cmdInfo, longValueList);\n\n    // ------------ String ------------\n    // 广播 String\n    String stringValue = \"1\";\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessage(cmdInfo, stringValue);\n    // 广播 String list\n    List<String> stringValueList = List.of(\"1L\", \"2L\");\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessageStringList(cmdInfo, stringValueList);\n            \n    // ------------ boolean ------------\n    // 广播 boolean\n    boolean boolValue = true;\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessage(cmdInfo, boolValue);\n    // 广播 boolean list\n    List<Boolean> boolValueList = List.of(true, false);\n    RangeBroadcaster.of(flowContext)\n            .setResponseMessageBoolList(cmdInfo, boolValueList);\n}\n```\n\n<br>\n\n[#301](https://github.com/iohao/ioGame/issues/301) FlowContext 更新元信息后，需要立即生效（跨服调用时）\n\n> 在此之前，更新元信息后，并不会将元信息同步到 FlowContext 中，只会将元信息同步到游戏对外服中；所以在更新元信息后，紧接着执行跨服调用是不能获取新的元信息内容的。\n>\n> 当前 issues 会对这部分做增强，也就是在更新元信息后，会将元信息同步到 FlowContext 中；这样，在后续的跨服调用中也能获取到最新的元信息。\n\n```java\nvoid test1(FlowContext flowContext) {\n    // 获取元信息\n    MyAttachment attachment = flowContext.getAttachment(MyAttachment.class);\n    attachment.nickname = \"渔民小镇\";\n\n    // [同步]更新 - 将元信息同步到玩家所在的游戏对外服中\n    flowContext.updateAttachment(attachment);\n\n    // 跨服请求\n    CmdInfo helloCmdInfo = CmdInfo.of(1, 1);\n    flowContext.invokeModuleMessage(helloCmdInfo);\n}\n\n@ActionController(1)\npublic class DemoFightAction {\n    @ActionMethod(1)\n    void hello(FlowContext flowContext) {\n        // 可以得到最新的元信息\n        MyAttachment attachment = flowContext.getAttachment(MyAttachment.class);\n        log.info(\"{}\", attachment.nickname);\n    }\n}\n```\n\n<br>\n\n [#293](https://github.com/iohao/ioGame/issues/293) 广播文档构建器支持对参数的单独描述\n\n```java\n  private void extractedDco(BarSkeletonBuilder builder) {\n      // UserCmd\n      builder.addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.enterSquare))\n              .setDataClass(SquarePlayer.class)\n              .setDescription(\"新玩家加入房间，给房间内的其他玩家广播\")\n      ).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.offline))\n              .setDataClass(LongValue.class, \"userId\")\n              .setDescription(\"有玩家下线了\")\n      );\n}\n```\n\n>  下面是生成后的对接文档预览\n\n```text\n==================== FightHallAction 大厅（类似地图） ====================\n \n路由: 1 - 2  --- 【进入大厅】 --- 【FightHallAction:94】【enterSquare】\n    方法参数: EnterSquare enterSquare 进入大厅\n    方法返回值: ByteValueList<SquarePlayer> 所有玩家\n    广播推送: SquarePlayer ，(新玩家加入房间，给房间内的其他玩家广播)\n \n\n路由: 1 - 5  --- 【玩家下线】 --- 【FightHallAction:154】【offline】\n    方法返回值: void \n    广播推送: LongValue userId，(有玩家下线了)\n\n```\n\n\n\n**[内置 kit]** \n\n开放 TaskListener 接口，TaskListener 是 TaskKit 相关的任务监听接口。\n\n\n\nTaskListener 任务监听回调，使用场景有：一次性延时任务、任务调度、轻量可控的延时任务、轻量的定时入库辅助功能 ...等其他扩展场景。这些使用场景都有一个共同特点，即监听回调。接口提供了 4 个方法，如下\n\n1. CommonTaskListener.onUpdate()，监听回调\n2. CommonTaskListener.triggerUpdate()，是否触发 CommonTaskListener.onUpdate() 监听回调方法\n3. CommonTaskListener.onException(Throwable) ，异常回调。在执行 CommonTaskListener.triggerUpdate() 和 CommonTaskListener.onUpdate() 方法时，如果触发了异常，异常将被该方法捕获。\n4. CommonTaskListener.getExecutor()，指定执行器来执行上述方法，目的是不占用业务线程。\n\n\n\n\n---\n\n\n\n### 2024-05-19 - v21.8\n\nhttps://github.com/iohao/ioGame/releases/tag/21.8\n\n\n---\n\n**版本更新汇总**\n\n- [light-game-room]  [#278](https://github.com/iohao/ioGame/issues/278) 桌游类、房间类游戏的扩展模块，简化与规范化房间管理相关的、开始游戏流程相关的、玩法操作相关的相关扩展\n- [core]  [#290](https://github.com/iohao/ioGame/issues/290) 新增广播文档构建器，简化生成广播对接文档\n- [示例集合整理] 将 SimpleExample、SpringBootExample、ioGameWeb2Game、fxglSimpleGame FXGL + netty合并成一个示例项目。\n\n---\n\n\n\n**[core]**  \n\n[#290](https://github.com/iohao/ioGame/issues/290) 新增广播文档构建器，简化生成广播对接文档\n\n下面是使用示例\n\n```java\npublic class MyLogicServer extends AbstractBrokerClientStartup {\n    @Override\n    public BarSkeleton createBarSkeleton() {\n        // 业务框架构建器\n        BarSkeletonBuilder builder = ...\n        \n        // 错误码、广播、推送对接文档生成\n        extractedDco(builder);\n\n        return builder.build();\n    }\n    \n    private void extractedDco(BarSkeletonBuilder builder) {\n        // 错误码\n        Arrays.stream(GameCode.values()).forEach(builder::addMsgExceptionInfo);\n\n        // UserCmd\n        builder.addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.enterSquare))\n                .setDataClass(SquarePlayer.class)\n                .setDescription(\"新玩家加入房间，给房间内的其他玩家广播\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.move))\n                .setDataClass(SquarePlayerMove.class)\n                .setDescription(\"其他玩家的移动\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.offline))\n                .setDataClass(LongValue.class)\n                .setDescription(\"有玩家下线了。userId\")\n        );\n\n        // room\n        builder.addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.roomUpdateBroadcast))\n                .setDataClass(FightRoomNotice.class)\n                .setDescription(\"房间更新通知\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.playerEnterRoomBroadcast))\n                .setDataClass(FightPlayer.class)\n                .setDescription(\"有新玩家加入房间\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.enterRoom))\n                .setDataClass(FightEnterRoom.class)\n                .setDescription(\"玩家自己进入房间\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.dissolveRoomBroadcast))\n                .setDescription(\"解散房间\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.quitRoom))\n                .setDataClass(LongValue.class)\n                .setDescription(\"有玩家退出房间了。userId\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.ready))\n                .setDataClass(PlayerReady.class)\n                .setDescription(\"有玩家准备或取消准备了\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.nextRoundBroadcast))\n                .setDataClass(IntValue.class)\n                .setDescription(\"对局开始，通知玩家开始选择。round 当前对局数\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.operationBroadcast))\n                .setDataClass(LongValue.class)\n                .setDescription(\"通知其他玩家，有玩家做了选择。userId\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.littleSettleBroadcast))\n                .setDataClassList(FightRoundPlayerScore.class)\n                .setDescription(\"广播玩家对局分数\")\n        ).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.gameOverBroadcast))\n                .setDescription(\"游戏结束\")\n        );\n    }\n}\n```\n\n下面是生成后的对接文档预览\n\n```text\n\n==================== FightHallAction 大厅（类似地图） ====================\n路由: 1 - 1  --- 【登录】 --- 【FightHallAction:67】【loginVerify】\n    方法参数: LoginVerify loginVerify 登录验证\n    方法返回值: UserInfo 玩家信息\n \n路由: 1 - 2  --- 【进入大厅】 --- 【FightHallAction:95】【enterSquare】\n    方法参数: EnterSquare enterSquare 进入大厅\n    方法返回值: ByteValueList<SquarePlayer> 所有玩家\n    广播推送: SquarePlayer 新玩家加入房间，给房间内的其他玩家广播\n \n路由: 1 - 4  --- 【玩家移动】 --- 【FightHallAction:131】【move】\n    方法参数: SquarePlayerMove squarePlayerMove 玩家移动\n    方法返回值: void \n    广播推送: SquarePlayerMove 其他玩家的移动\n \n路由: 1 - 5  --- 【玩家下线】 --- 【FightHallAction:155】【offline】\n    方法返回值: void \n    广播推送: LongValue 有玩家下线了。userId\n \n\n==================== FightRoomAction  ====================\n路由: 2 - 1  --- 【玩家创建新房间】 --- 【FightRoomAction:63】【createRoom】\n    方法返回值: void \n \n路由: 2 - 2  --- 【玩家进入房间】 --- 【FightRoomAction:96】【enterRoom】\n    方法参数: LongValue roomId      房间号\n    方法返回值: void 房间信息\n    广播推送: FightEnterRoom 玩家自己进入房间\n \n路由: 2 - 3  --- 【玩家退出房间】 --- 【FightRoomAction:120】【quitRoom】\n    方法返回值: void \n    广播推送: LongValue 有玩家退出房间了。userId\n \n路由: 2 - 4  --- 【玩家准备】 --- 【FightRoomAction:146】【ready】\n    方法参数: BoolValue ready       true 表示准备，false 则是取消准备\n    方法返回值: void \n    广播推送: PlayerReady 有玩家准备或取消准备了\n \n路由: 2 - 5  --- 【房间列表】 --- 【FightRoomAction:222】【listRoom】\n    方法返回值: ByteValueList<FightRoomNotice> 房间列表\n \n路由: 2 - 6  --- 【玩家在游戏中的操作】 --- 【FightRoomAction:191】【operation】\n    方法参数: FightOperationCommand command     玩家操作数据\n    方法返回值: void \n \n路由: 2 - 7  --- 【开始游戏】 --- 【FightRoomAction:162】【startGame】\n    方法返回值: void \n \n\n==================== 其它广播推送 ====================\n路由: 2 - 51  --- 广播推送: FightRoomNotice (房间更新通知)\n路由: 2 - 50  --- 广播推送: FightPlayer (有新玩家加入房间)\n路由: 2 - 52  --- 广播推送: IntValue (对局开始，通知玩家开始选择。round 当前对局数)\n路由: 2 - 53  --- 广播推送: LongValue (通知其他玩家，有玩家做了选择。userId)\n路由: 2 - 56  --- 广播推送: none (解散房间)\n路由: 2 - 54  --- 广播推送: ByteValueList<FightRoundPlayerScore> (广播玩家对局分数)\n路由: 2 - 55  --- 广播推送: none (游戏结束)\n==================== 错误码 ====================\n -1008 : 绑定的游戏逻辑服不存在 \n -1007 : 强制玩家下线 \n -1006 : 数据不存在 \n -1005 : class 不存在 \n -1004 : 请先登录 \n -1003 : 心跳超时相关 \n -1002 : 路由错误 \n -1001 : 参数验错误 \n -1000 : 系统其它错误 \n 1 : 玩家在房间里 \n 3 : 房间不存在 \n 4 : 非法操作 \n 6 : 开始游戏需要的最小人数不足 \n 7 : 请等待其他玩家准备 \n 8 : 房间空间不足，人数已满 \n\n```\n\n---\n\n\n\n**[light-game-room]**\n\n\n[#278](https://github.com/iohao/ioGame/issues/278) 桌游类、房间类游戏的扩展模块，简化与规范化房间管理相关的、开始游戏流程相关的、玩法操作相关的相关扩展\n\n\n\nlight-game-room 房间，是 ioGame 提供的一个轻量小部件 - 可按需选择的模块。\n\n> **light-game-room + 领域事件 + 内置 Kit  = 轻松搞定桌游类游戏**\n\n\n\n该模块是桌游类、房间类游戏的解决方案。比较适合桌游类、房间类的游戏基础搭建，基于该模型可以做一些如，炉石传说、三国杀、斗地主、麻将 ...等类似的桌游。或者说只要是房间类的游戏，该模型都适用。比如，CS、泡泡堂、飞行棋、坦克大战 ...等。\n\n\n\n如果你计划做一些桌游类的游戏，那么推荐你基于该模块做扩展。该模块遵循面向对象的设计原则，没有强耦合，可扩展性强。且帮助开发者屏蔽了很多重复性的工作，并可为项目中的功能模块结构、开发流程等进行清晰的组织定义，减少了后续的项目维护成本。\n\n\n\n**主要解决的问题与职责**\n\n桌游、房间类的游戏在功能职责上可以分为 3 大类，分别是\n\n1. **房间管理相关的**\n   1. 管理着所有的房间、查询房间列表、房间的添加、房间的删除、房间与玩家之间的关联、房间查找（通过 roomId 查找、通过 userId 查找）。\n1. **开始游戏流程相关的**\n   1. 通常桌游、房间类的游戏都有一些固定的流程，如创建房间、玩家进入房间、玩家退出房间、解散房间、玩家准备、开始游戏 ...等。\n   2. 开始游戏时，需要做开始前的验证，如房间内的玩家是否符足够 ...等，当一切符合业务时，才是真正的开始游戏。\n1. **玩法操作相关的**\n   1. 游戏开始后，由于不同游戏之间的具体操作是不相同的。如坦克的射击，炉石的战前选牌、出牌，麻将的吃、碰、杠、过、胡，回合制游戏的普攻、防御、技能 ...等。\n   2. 由于玩法操作的不同，所以我们的玩法操作需要是可扩展的，并用于处理具体的玩法操作。同时这种扩展方式更符合单一职责，使得我们后续的扩展与维护成本更低。\n\n以上功能职责（房间管理相关、流程相关、玩法操作相关）属于相对通用的功能。如果每款游戏都重复的做这些工作，除了枯燥之外，还将浪费巨大的人力成本。\n\n而当前模块则能很好的帮助开发者屏蔽这些重复性的工作，并可为项目中的功能模块结构、开发流程等进行清晰的组织定义，减少了后续的项目维护成本。更重要的是有相关文档，将来当你的团队有新进成员时，可以快速的上手。\n\n---\n\n\n\n**room 实战简介**\n\n\n文档中，我们基于该 room 模块做一个实战示例，该示例整体比较简单，多名玩家在房间里猜拳（石头、剪刀、布）得分。实战示例包括了前后端，前端使用 [FXGL](https://github.com/almasB/FXGL) 引擎，这样开发者在学习时，只需 JDK 环境就可以了，而不需要安装更多的环境。启动游戏后玩家会将加入大厅（类似地图），多名玩家相互可见，并且玩家可以在大厅内移动。\n\n\n\n---\n\n\n\n[示例集合整理] \n\n\n\n\n| github                                                     | gitee                                                     |\n| ---------------------------------------------------------- | --------------------------------------------------------- |\n| [ioGame 示例集合](https://github.com/iohao/ioGameExamples) | [ioGame 示例集合](https://gitee.com/iohao/ioGameExamples) |\n\n---\n\n\n\n### 2024-05-11 - v21.7\n\nhttps://github.com/iohao/ioGame/releases/tag/21.7\n\n\n\n**版本更新汇总**\n\n1. [core]  [#112](https://github.com/iohao/ioGame/issues/112) protobuf 协议类添加检测，通过 action 构建时的监听器实现\n2. [core]  [#272](https://github.com/iohao/ioGame/issues/272) 业务框架 - 提供 action 构建时的监听回调\n3. [core]  [#274](https://github.com/iohao/ioGame/issues/274) 优化、提速 - 预生成 jprotobuf 协议类的代理，通过 action 构建时的监听器实现\n4. [broker] fix [#277](https://github.com/iohao/ioGame/issues/277) 、[#280](https://github.com/iohao/ioGame/issues/280) 偶现 BrokerClientType 为空\n5. [external]  [#271](https://github.com/iohao/ioGame/issues/271) 游戏对外服 - 内置与可选 handler - log 相关的打印（触发异常、断开连接时）\n6. [room] 简化命名:  AbstractPlayer --> Player、AbstractRoom --> Room\n7. 其他优化：预先生成游戏对外服统一协议的代理类及内置的协议碎片相关代理类，优化 action 参数解析\n\n\n\n**[external]**\n\n[#271](https://github.com/iohao/ioGame/issues/271) 游戏对外服 - 内置与可选 handler - log 相关的打印（触发异常、断开连接时）\n\n\n\n\n**[core]**\n\n[#272](https://github.com/iohao/ioGame/issues/272) 业务框架 - 提供 action 构建时的监听回调\n\n开发者可以利用 ActionParserListener 接口来观察 action 构建过程，或者做一些额外的扩展。\n\n\n\n扩展示例参考\n\n```java\n// 简单打印\npublic final class YourActionParserListener implements ActionParserListener {\n    @Override\n    public void onActionCommand(ActionParserContext context) {\n        ActionCommand actionCommand = context.getActionCommand();\n        log.info(actionCommand);\n    }\n}\n\nvoid test() {\n    BarSkeletonBuilder builder = ...;\n    builder.addActionParserListener(new YourActionParserListener());\n}\n```\n\n\n\n[#112](https://github.com/iohao/ioGame/issues/112) protobuf 协议类添加检测，通过 action 构建时的监听器实现\n\n如果当前使用的编解码器为 ProtoDataCodec 时，当 action 的参数或返回值的类没有添加 ProtobufClass 注解时（通常是忘记添加），给予一些警告提示。\n\n```java\n// 该协议类没有添加 ProtobufClass 注解\nclass Bird {\n    public String name;\n}\n\n@ActionController(1)\npublic class MyAction {\n    @ActionMethod(1)\n    public Bird testObject() {\n        return new Bird();\n    }\n}\n======== 注意，协议类没有添加 ProtobufClass 注解 ========\nclass com.iohao.game.action.skeleton.core.action.Bird\n```\n\n\n\n[#274](https://github.com/iohao/ioGame/issues/274) 优化、提速 - 预生成 jprotobuf 协议类的代理，通过 action 构建时的监听器实现\n\n如果当前使用的编解码器为 ProtoDataCodec 时，会在启动时就预先生成好 jprotobuf 协议类对应的代理类（用于 .proto 相关的 编码、解码），而不必等到用时在创建该代理类。从而达到整体优化提速的效果。\n\n \n\n在此之前，在没做其他设置的情况下，首次访问 action 时，如果参数使用的 jprotobuf 协议类，那么在解码该参数时，会通过 `ProtobufProxy.create` 来创建对应的代理类（类似 .proto 相关的 编码、解码）。之后再访问时，才会从缓存中取到对应的代理类。\n\n \n\n该优化默认开启，开发者可以不需要使用与配置跟 jprotobuf-precompile-plugin 插件相关的了。\n\n\n\n已经预先生成的代理类有\n\n- 游戏对外服统一协议 ExternalMessage\n- 所有开发者定义的 action 的方法参数及返回值\n- 解决协议碎片相关，如 int、int list、String、String list、long、long list、ByteValueList ...等\n\n\n\n**[room]**\n简化命名:  AbstractPlayer --> Player、AbstractRoom --> Room\n\n\n\n**其他优化**\n\n优化 action 参数解析\n\n---\n\n\n\n### 2024-04-23 - v21.6\n\nhttps://github.com/iohao/ioGame/releases/tag/21.6\n\n\n\n**版本更新汇总**\n\n1. [#264](https://github.com/iohao/ioGame/issues/264) 新增属性值变更监听特性\n2. 模拟客户端新增与服务器断开连接的方法。模拟客户端新增是否活跃的状态属性。\n3. [#265](https://github.com/iohao/ioGame/issues/265) 从游戏对外服中获取玩家相关数据 - 模拟玩家请求。\n4. 任务相关：TaskListener 接口增加异常回调方法，用于接收异常信息；当 triggerUpdate 或 onUpdate 方法抛出异常时，将会传递到该回调方法中。\n5. [#266](https://github.com/iohao/ioGame/issues/266) 新增 RangeBroadcast 范围内的广播功能，这个范围指的是，可指定某些用户进行广播。\n6. AbstractRoom 增加 ifPlayerExist、ifPlayerNotExist 方法。\n\n------\n\n**属性监听特性**\n\n[#264](https://github.com/iohao/ioGame/issues/264) 新增属性值变更监听特性\n\n\n属性可添加监听器，当某些属性值的发生变化时，触发监听器。\n\n\n\n**使用场景举例**\n\n比如玩家的血量低于一定值时，需要触发无敌状态；此时，我们就可以监听玩家的血量，并在该属性上添加一个对应的监听器来观察血量的变化，当达到预期值时就触发对应的业务。\n\n \n\n类似的使用场景还有很多，这里就不过多的举例了。属性监听的特点在于属性变化后会触发监听器。\n\n\n\n**属性监听特点**\n\n- 可为属性添加监听器，用于观察属性值的变化。\n- 属性可以添加多个监听器。\n- 属性的监听器可以移除。\n\n\n\n**框架已经内置了几个属性实现类，分别是：**\n\n- IntegerProperty\n- LongProperty\n- StringProperty\n- BooleanProperty\n- ObjectProperty\n\n------\n\nfor example - 添加监听器\n\nBooleanProperty\n\n当 BooleanProperty 对象的值发生改变时，触发监听器。\n\n```java\nvar property = new BooleanProperty();\n// 添加一个监听器。\nproperty.addListener((observable, oldValue, newValue) -> {\n   log.info(\"oldValue:{}, newValue:{}\", oldValue, newValue);\n});\n\nproperty.get(); // value is false\nproperty.set(true); // 值变更时，将会触发监听器\nproperty.get(); // value is true\n```\n\n\n\nIntegerProperty\n\n当 IntegerProperty 对象的值发生改变时，触发监听器。\n\n```java\nvar property = new IntegerProperty();\n// add listener monitor property object\nproperty.addListener((observable, oldValue, newValue) -> {\n   log.info(\"oldValue:{}, newValue:{}\", oldValue, newValue);\n});\n\nproperty.get(); // value is 0\nproperty.set(22); // When the value changes,listeners are triggered\nproperty.get(); // value is 22\n\nproperty.increment(); // value is 23. will trigger listeners\n```\n\n------\n\nfor example - 移除监听器\n\n下面这个示例，我们将 property 初始值设置为 10，随后添加了一个监听器；当监听器观察到新值为 9 时，就从 observable 中移除自己（这个自己指的是监听器本身），而 observable 则是 IntegerProperty。\n\n```java\n@Test\npublic void remove1() {\n    IntegerProperty property = new IntegerProperty(10);\n    // 添加一个监听器\n    property.addListener(new PropertyChangeListener<>() {\n        @Override\n        public void changed(PropertyValueObservable<? extends Number> observable, Number oldValue, Number newValue) {\n            log.info(\"1 - newValue : {}\", newValue);\n\n            if (newValue.intValue() == 9) {\n                // 移除当前监听器\n                observable.removeListener(this);\n            }\n        }\n    });\n\n    property.decrement(); // value 是 9，并触发监听器\n    property.decrement(); // value 是 8，由于监听器已经移除，所以不会触发任何事件。\n}\n```\n\n\n\n下面的示例中，我们定义了一个监听器类 OnePropertyChangeListener 并实现了 PropertyChangeListener 监听器接口。示例中，我们通过 OnePropertyChangeListener 对象的引用来移除监听器。\n\n```java\n@Test\npublic void remove2() {\n    // 监听器\n    OnePropertyChangeListener onePropertyChangeListener = new OnePropertyChangeListener();\n    \n    // 属性\n    IntegerProperty property = new IntegerProperty();\n    // 添加监听器\n    property.addListener(onePropertyChangeListener);\n\n    property.increment(); // value == 1，并触发监听器\n    property.removeListener(onePropertyChangeListener); // 移除监听器\n    property.increment(); // value == 2，由于监听器已经移除，所以不会触发任何事件。\n}\n\n// 自定义的监听器\nclass OnePropertyChangeListener implements PropertyChangeListener<Number> {\n    @Override\n    public void changed(PropertyValueObservable<? extends Number> observable, Number oldValue, Number newValue) {\n        log.info(\"oldValue:{}, newValue:{}, observable:{}\", oldValue, newValue, observable);\n    }\n}\n```\n\n------\n\n**属性监听 - 小结**\n\n属性监听在使用上是简单的，如果你的业务中**有关于属性变化后需要触发某些事件的**，可以考虑引用该特性。框架为 int、long、boolean、Object、String 等基础类型提供了对应的属性监听。\n\n\n\n属性监听特性支持添加多个监听器，支持移除监听器。\n\n------\n\n\n\n**模拟客户端相关**\n\n- 模拟客户端新增与服务器断开连接的方法。\n- 模拟客户端新增是否活跃的状态属性。\n\n```java\nClientUser clientUser = ...;\n// 是否活跃，true 表示玩家活跃\nclientUser.isActive();\n// 关闭模拟客户端连接\nclientUser.getClientUserChannel().closeChannel();\n```\n\n------\n\n\n\n**访问游戏对外服与扩展相关**\n\n\nRequestCollectExternalMessage 增加 userId 字段。\n\n\n\n[#265](https://github.com/iohao/ioGame/issues/265) 模拟玩家请求时 - 从游戏对外服中获取在线玩家相关数据\n\n新增  UserHeadMetadataExternalBizRegion，从用户（玩家）所在游戏对外服中获取用户自身的数据，如用户所绑定的游戏逻辑服、元信息 ...等\n\n```java\n@Slf4j\n@RestController\n@RequestMapping(\"other\")\npublic class OtherController {\n    static final AtomicLong msgId = GameManagerController.msgId;\n    /** 为了方便测试，这里指定一个 userId 来模拟玩家 */\n    static final long userId = GameManagerController.userId;\n\n    @GetMapping(\"/notice\")\n    public String notice() {\n        log.info(\"other notice\");\n        // 使用协议碎片特性 https://iohao.github.io/game/docs/manual/protocol_fragment\n        StringValue data = StringValue.of(\"other GM web msg \" + msgId.incrementAndGet());\n        // 模拟请求 : 路由 - 业务数据\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(ExchangeCmd.of(ExchangeCmd.notice), data);\n\n        // 设置需要模拟的玩家\n        HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n        headMetadata.setUserId(userId);\n\n        // 从游戏对外服中获取一些用户（玩家的）自身的数据，如元信息、所绑定的游戏逻辑服 ...等\n        Optional<HeadMetadata> headMetadataOptional = ExternalCommunicationKit.employHeadMetadata(requestMessage);\n\n        if (headMetadataOptional.isPresent()) {\n            // 发起模拟请求\n            extractedRequestLogic(requestMessage);\n\n            // 打印从游戏对外服获取的元信息\n            byte[] attachmentData = headMetadata.getAttachmentData();\n            ExchangeAttachment attachment = DataCodecKit.decode(attachmentData, ExchangeAttachment.class);\n            return \"other notice 玩家的元信息: %s - %s\".formatted(attachment, msgId.get());\n        } else {\n            return \"other notice 玩家 %s 不在线，无法获取玩家的元信息 - %s\".formatted(userId, msgId.get());\n        }\n    }\n\n    private void extractedRequestLogic(RequestMessage requestMessage) {\n        // 向逻辑服发送请求，该模拟请求具备了玩家的元信息。\n        BrokerClient brokerClient = MyKit.brokerClient;\n        InvokeModuleContext invokeModuleContext = brokerClient.getInvokeModuleContext();\n        invokeModuleContext.invokeModuleVoidMessage(requestMessage);\n    }\n}\n```\n\n------\n\n\n\n**任务工具相关**\n\nTaskListener 接口增加异常回调方法 `void onException(Throwable e)`，用于接收异常信息；当 triggerUpdate 或 onUpdate 方法抛出异常时，将会传递到该回调方法中。\n\n```java\n@Test\npublic void testException() throws InterruptedException {\n    AtomicBoolean hasEx = new AtomicBoolean(false);\n    TaskKit.runOnce(new OnceTaskListener() {\n        @Override\n        public void onUpdate() {\n            // 模拟一个业务异常\n            throw new RuntimeException(\"hello exception\");\n        }\n\n        @Override\n        public void onException(Throwable e) {\n            hasEx.set(true);\n            // 触发异常后，将来到这里\n            log.error(e.getMessage(), e);\n        }\n    }, 10, TimeUnit.MILLISECONDS);\n\n    TimeUnit.MILLISECONDS.sleep(200);\n    Assert.assertTrue(hasEx.get()); // true\n}\n```\n\n------\n\n\n\n**业务框架相关 - [common-core]**\n\n[#266](https://github.com/iohao/ioGame/issues/266) 新增 RangeBroadcast 范围内的广播功能，这个范围指的是，可指定某些用户进行广播。\n\n\n\n在执行广播前，开发者可以自定义业务逻辑，如\n\n- 添加一些需要广播的用户\n- 删除一些不需要接收广播的用户\n- 可通过重写 logic、trick 方法来做一些额外扩展\n\n```java\n// example - 1\nnew RangeBroadcast(flowContext)\n        // 需要广播的数据\n        .setResponseMessage(responseMessage)\n        // 添加需要接收广播的用户\n        .addUserId(1)\n        .addUserId(2)\n        .addUserId(List.of(3L, 4L, 5L))\n        // 排除一些用户，被排除的用户将不会接收到广播\n        .removeUserId(1)\n        // 执行广播\n        .execute();\n\n// example - 2\nnew RangeBroadcast(flowContext)\n        // 需要广播的数据\n        .setResponseMessage(cmdInfo, playerReady)\n        // 添加需要接收广播的用户\n        .addUserId(1)\n        // 执行广播\n        .execute();\n```\n\n------\n\n**[light-game-room] 房间模块**\n\n- 移除 AbstractRoom broadcast 系列方法，开发者可使用 RoomBroadcastFlowContext 接口实现旧的兼容。\n- 移除 AbstractRoom createSend 方法，开发者可使用 ofRangeBroadcast 系列来代替。AbstractRoom 新增 RoomBroadcastEnhance，实现房间内的广播增强，该系列在语义上更清晰。\n\n\n\n```java\nfinal RoomService roomService = ...;\n\n@ActionMethod(RoomCmd.ready)\npublic void ready(boolean ready, FlowContext flowContext) {\n    long userId = flowContext.getUserId();\n    // 得到玩家所在的房间\n    AbstractRoom room = this.roomService.getRoomByUserId(userId);\n    \n    // 准备\n    PlayerReady playerReady = new PlayerReady();\n    playerReady.userId = userId;\n    playerReady.ready = ready;\n  \n    // 通知房间内的所有玩家，有玩家准备或取消准备了\n    room.ofRangeBroadcast(flowContext)\n            // 响应数据（路由、业务数据）\n            .setResponseMessage(flowContext.getCmdInfo(), playerReady)\n            .execute();\n}\n\n// 准备或取消准备\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class PlayerReady {\n    /** 当前操作的玩家 userId */\n    long userId;\n    /** true 表示准备 */\n    boolean ready;\n}\n```\n\n------\n\n\n\n**AbstractRoom 增加 ifPlayerExist、ifPlayerNotExist 方法。**\n\n**ifPlayerExist 方法**\n\n如果玩家在房间内，就执行给定的操作，否则不执行任何操作。\n\n```java\nRoomService roomService = ...;\nAbstractRoom room = ...;\n// 如果玩家不在房间内，就创建一个玩家，并让玩家加入房间\nroom.ifPlayerNotExist(userId, () -> {\n    // 玩家加入房间\n    FightPlayerEntity newPlayer = new FightPlayerEntity();\n    newPlayer.setId(userId);\n    \n    this.roomService.addPlayer(room, newPlayer);\n});\n```\n\n\n\n**ifPlayerNotExist 方法**\n\n如果玩家不在房间内，就执行给定的操作，否则不执行任何操作。\n\n```java\nAbstractRoom room = ...;\n// 有新玩家加入房间，通知其他玩家\nroom.ifPlayerExist(userId, (FightPlayerEntity playerEntity) -> {\n    FightPlayer fightPlayer = FightMapstruct.ME.convert(playerEntity);\n    room.ofRangeBroadcast(flowContext)\n            .setResponseMessage(RoomCmd.of(RoomCmd.playerEnterRoomBroadcast), fightPlayer)\n            // 排除不需要通知的玩家（当前 userId 是自己）\n            .removeUserId(userId)\n            .execute();\n});\n```\n\n---\n\n\n\n### 2024-04-16 - v21.5\n\nhttps://github.com/iohao/ioGame/releases/tag/21.5\n\n\n\n1.  增强 ClassScanner 类 \n2.  优化模拟客户端 \n3.  [#258](about:blank) 文档生成，兼容 gradle 编译路径 \n4.  enhance jprotobuf，临时解决打包后不能在 linux java21 环境运行的问题，see [java21，springBoot3.2 打 jar 后使用异常 · Issue #211 · jhunters/jprotobuf (github.com)](https://github.com/jhunters/jprotobuf/issues/211) \n5.  生成 .proto 时，在最后打印文件路径 \n\n1. [#255](https://github.com/iohao/ioGame/issues/255) 关于 Proto 生成排除属性问题\n\n```java\n/**\n * 动物\n */\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class Animal {\n    /** id */\n    int id;\n    /** 动物类型 - 枚举测试 */\n    AnimalType animalType;\n  \t/** 年龄 - 忽略的属性*/\n    @Ignore\n    String age;\n}\n```\n\n\n\n生成后的 .proto\n\n```protobuf\n// 动物\nmessage Animal {\n  // id\n  int32 id = 1;\n  // 动物类型 - 枚举测试\n  AnimalType animalType = 2;\n}\n```\n\n---\n\n\n\n### 2024-03-28 - v21.4\n\nhttps://github.com/iohao/ioGame/releases/tag/21.4\n\n\n\n\n[ #253](https://github.com/iohao/ioGame/issues/253)\n\n> CreateRoomInfo.createUserId int --> long\n\n\n\nExecutorRegion\n\n> 1. 优化默认创建策略\n> 2. 优化 ExecutorRegionKit，SimpleThreadExecutorRegion 默认使用全局单例，减少对象的创建。\n\n\n\nproto 文档生成时，默认指定为 StandardCharsets.UTF_8\n\n>  javaProjectBuilder.setEncoding(StandardCharsets.UTF_8.name());\n\n\n\n玩家下线时，使用自身所关联的线程处理。\n\n>  SocketUserSessions removeUserSession\n\n```java\npublic void removeUserSession(SocketUserSession userSession) {\n\n    if (Objects.isNull(userSession)) {\n        return;\n    }\n\n    long userId = userSession.getUserId();\n    ExecutorRegionKit.getSimpleThreadExecutor(userId)\n            .executeTry(() -> internalRemoveUserSession(userSession));\n}\n```\n\n---\n\n\n\n### 2024-03-11 - v21.3\n\nhttps://github.com/game-town/ioGame/releases/tag/21.3\n\n\n\n[#250](https://github.com/game-town/ioGame/issues/250) 游戏对外服 - 自定义编解码 - WebSocketMicroBootstrapFlow\n\n\n\n\n重写 WebSocketMicroBootstrapFlow createExternalCodec 方法，用于创建开发者自定义的编解码，其他配置则使用 pipelineCodec 中的默认配置。\n\n```java\nDefaultExternalServerBuilder builder = ...;\n\nbuilder.setting().setMicroBootstrapFlow(new WebSocketMicroBootstrapFlow() {\n    @Override\n    protected MessageToMessageCodec<BinaryWebSocketFrame, BarMessage> createExternalCodec() {\n        // 开发者自定义的编解码实现类。\n        return new YourWsExternalCodec();\n    }\n});\n```\n\n\n\n以下展示的是 WebSocketMicroBootstrapFlow pipelineCodec 相关代码\n\n```java\npublic class WebSocketMicroBootstrapFlow extends SocketMicroBootstrapFlow {\n    ... 省略部分代码\n    @Override\n    public void pipelineCodec(PipelineContext context) {\n        // 添加 http 相关 handler\n        this.httpHandler(context);\n\n        // 建立连接前的验证 handler\n        this.verifyHandler(context);\n\n        // 添加 websocket 相关 handler\n        this.websocketHandler(context);\n\n        // websocket 编解码\n        var externalCodec = this.createExternalCodec();\n        context.addLast(\"codec\", externalCodec);\n    }\n\n    @Override\n    protected MessageToMessageCodec<BinaryWebSocketFrame, BarMessage> createExternalCodec() {\n        // createExternalCodec 相当于一个钩子方法。\n        return new WebSocketExternalCodec();\n    }\n};\n```\n\n\n\n[#249](https://github.com/game-town/ioGame/issues/249)\n将集群启动顺序放到 Broker（游戏网关）之后。\n\n集群增减和逻辑服 Connect 增减使用同一线程处理。\n\nIoGameGlobalConfig brokerClusterLog 集群相关日志不开启。\n\n---\n\n\n\n### 2024-02-22 - v21.2\n\n修复版本号显示错误问题（该版本没有功能上的更新与修改，不升级也不影响）\n\n\n\n###  2024-02-21 - v21.1\n\nhttps://github.com/game-town/ioGame/releases/tag/21.1\n\n\n\nioGame21 首发计划\n\n| 功能支持                                                                                                                                                                                           | 完成 | 描述        | issu                                                   |\n|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----|-----------|--------------------------------------------------------|\n| 游戏对外服开放自定义协议                                                                                                                                                                                   | ✅  | 功能增强      | [#213](https://github.com/game-town/ioGame/issues/213) |\n| 游戏对外服缓存                                                                                                                                                                                        | ✅  | 功能增强、性能提升 | [#76](https://github.com/game-town/ioGame/issues/76)   |\n| FlowContext 增加通信能力，提供同步、异步、异步回调的便捷使用                                                                                                                                                           | ✅  | 功能增强      | [#235](https://github.com/game-town/ioGame/issues/235) |\n| 虚拟线程支持;  各逻辑服之间通信阻塞部分，改为使用虚拟线程，避免阻塞业务线程                                                                                                                                                        | ✅  | 功能增强、性能提升 |                                                        |\n| 默认不使用 bolt 线程池，减少上下文切换。  ioGame17：netty --> bolt 线程池 --> ioGame 线程池。  ioGame21： 1. netty --> ioGame 线程池。 2. 部分业务将直接在 netty 线程中消费业务。 | ✅  | 性能提升      |                                                        |\n| 全链路调用日志跟踪；日志增强 traceId                                                                                                                                                                         | ✅  | 功能增强      | [#230](https://github.com/game-town/ioGame/issues/230) |\n| 移除过期代码                                                                                                                                                                                         | ✅  | 整理        | [#237](https://github.com/game-town/ioGame/issues/239) |\n| 分布式事件总线可以代替 redis pub sub 、 MQ ，并且具备全链路调用日志跟踪，这点是中间件产品做不到的。                                                                                                                                    | ✅  | 功能增强      | [#228](https://github.com/game-town/ioGame/issues/228) |\n| 日志库使用新版本 slf4j 2.0                                                                                                                                                                             | ✅  |           |                                                        |\n| 心跳响应前的回调                                                                                                                                                                                       | ✅  | 功能增强      | [#234](https://github.com/game-town/ioGame/issues/234) |\n| FlowContext 增加更新、获取元信息的便捷使用                                                                                                                                                                    | ✅  | 功能增强      | [#236](https://github.com/game-town/ioGame/issues/236) |\n\n\n\n### 2024\n\n#### ioGame21 首发内容简介\n\n在 ioGame21 中，该版本做了数百项优化及史诗级增强。\n\n- 文档方面\n- 线程管理域方面的开放与统一、减少线程池上下文切换\n- FlowContext 得到了**史诗级**的增强。\n- 新增通讯方式 - 分布式事件总线\n- 游戏对外服方面增强\n- 全链路调用日志跟踪\n- 各逻辑服之间通信阻塞部分，改为使用虚拟线程, 避免阻塞业务线程，从而使得框架的吞吐量得到了巨大的提升。\n\n\n\n##### 游戏对外服相关\n\n[#76](https://github.com/game-town/ioGame/issues/76) 游戏对外服缓存 \n\n\n\n\n游戏对外服缓存，可以将一些热点的业务数据缓存在游戏对外服中，玩家每次访问相关路由时，会直接从游戏对外服的内存中取数据。这样可以避免反复请求游戏逻辑服，从而达到性能的超级提升；\n\n```java\nprivate static void extractedExternalCache() {\n    // 框架内置的缓存实现类\n    DefaultExternalCmdCache externalCmdCache = new DefaultExternalCmdCache();\n    // 添加到配置中\n    ExternalGlobalConfig.externalCmdCache = externalCmdCache;\n    // 配置缓存 3-1\n    externalCmdCache.addCmd(3, 1);\n}\n```\n\n\n\n[#213](https://github.com/game-town/ioGame/issues/213) 游戏对外服开放自定义协议 \n\n\n开发者可自定义游戏对外服协议，用于代替框架默认的 ExternalMessage 公共对外协议。\n\n\n\n[#234](https://github.com/game-town/ioGame/issues/234) 心跳响应前的回调 \n\n\n在部分场景下，在响应心跳前可添加当前时间，使得客户端与服务器时间同步。\n\n```java\n@Slf4j\npublic class DemoIdleHook implements SocketIdleHook {\n    ... ... 省略部分代码\n    volatile byte[] timeBytes;\n\n    public DemoIdleHook() {\n        updateTime();\n        // 每秒更新当前时间\n        TaskKit.runInterval(this::updateTime, 1, TimeUnit.SECONDS);\n    }\n\n    private void updateTime() {\n        LongValue data = LongValue.of(TimeKit.currentTimeMillis());\n        // 避免重复序列化，这里提前序列化好时间数据\n        timeBytes = DataCodecKit.encode(data);\n    }\n\n    @Override\n    public void pongBefore(BarMessage idleMessage) {\n        // 把当前时间戳给到心跳接收端\n        idleMessage.setData(timeBytes);\n    }\n}\n```\n\n\n\n##### FlowContext - 跨服通信\n\n[#235](https://github.com/game-town/ioGame/issues/235) FlowContext 增加通信能力，提供同步、异步、异步回调的便捷使用 \n\n\n```java\n// 跨服请求 - 同步、异步回调演示\nvoid invokeModuleMessage() {\n    // 路由、请求参数\n    ResponseMessage responseMessage = flowContext.invokeModuleMessage(cmdInfo, yourData);\n    RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);\n    log.info(\"同步调用 : {}\", roomNumMsg.roomCount);\n\n    // --- 此回调写法，具备全链路调用日志跟踪 ---\n    // 路由、请求参数、回调\n    flowContext.invokeModuleMessageAsync(cmdInfo, yourData, responseMessage -> {\n        RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);\n        log.info(\"异步回调 : {}\", roomNumMsg.roomCount);\n    });\n}\n\n// 广播\npublic void broadcast(FlowContext flowContext) {\n    // 全服广播 - 路由、业务数据\n    flowContext.broadcast(cmdInfo, yourData);\n\n    // 广播消息给单个用户 - 路由、业务数据、userId\n    long userId = 100;\n    flowContext.broadcast(cmdInfo, yourData, userId);\n\n    // 广播消息给指定用户列表 - 路由、业务数据、userIdList\n    List<Long> userIdList = new ArrayList<>();\n    userIdList.add(100L);\n    userIdList.add(200L);\n    flowContext.broadcast(cmdInfo, yourData, userIdList);\n\n    // 给自己发送消息 - 路由、业务数据\n    flowContext.broadcastMe(cmdInfo, yourData);\n\n    // 给自己发送消息 - 业务数据\n    // 路由则使用当前 action 的路由。\n    flowContext.broadcastMe(yourData);\n}\n```\n\n\n\n[#236](https://github.com/game-town/ioGame/issues/236) FlowContext 增加更新、获取元信息的便捷使用 \n\n\n```java\nvoid test(MyFlowContext flowContext) {\n    // 获取元信息\n    MyAttachment attachment = flowContext.getAttachment();\n    attachment.nickname = \"渔民小镇\";\n\n    // [同步]更新 - 将元信息同步到玩家所在的游戏对外服中\n    flowContext.updateAttachment();\n\n    // [异步无阻塞]更新 - 将元信息同步到玩家所在的游戏对外服中\n\tflowContext.updateAttachmentAsync();\n}\n\npublic class MyFlowContext extends FlowContext {\n    MyAttachment attachment;\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public MyAttachment getAttachment() {\n        if (Objects.isNull(attachment)) {\n            this.attachment = this.getAttachment(MyAttachment.class);\n        }\n\n        return this.attachment;\n    }\n}\n```\n\n\n\n##### 线程相关 - 无锁高并发\n\n\n\n\n虚拟线程支持，各逻辑服之间通信阻塞部分使用虚拟线程来处理，避免阻塞业务线程。\n\n\n\n默认不使用 bolt 线程池，减少上下文切换。ioGame21 业务消费的线程相关内容如下：\n\n1. netty --> ioGame 线程池。\n2. 部分业务将直接在 netty 线程中消费业务。\n\n\n\n在 ioGame21 中，框架内置了 3 个线程执行器管理域，分别是\n\n1. UserThreadExecutorRegion ，用户线程执行器管理域。\n2. UserVirtualThreadExecutorRegion ，用户虚拟线程执行器管理域。\n3. SimpleThreadExecutorRegion ，简单的线程执行器管理域。\n\n\n\n**从工具类中得到与用户（玩家）所关联的线程执行器**\n\n```java\n@Test\npublic void userThreadExecutor() {\n    long userId = 1;\n\n    ThreadExecutor userThreadExecutor = ExecutorRegionKit.getUserThreadExecutor(userId);\n\n    userThreadExecutor.execute(() -> {\n        // print 1\n        log.info(\"userThreadExecutor : 1\");\n    });\n\n    userThreadExecutor.execute(() -> {\n        // print 2\n        log.info(\"userThreadExecutor : 2\");\n    });\n}\n\n@Test\npublic void getUserVirtualThreadExecutor() {\n    long userId = 1;\n\n    ThreadExecutor userVirtualThreadExecutor = ExecutorRegionKit.getUserVirtualThreadExecutor(userId);\n\n    userVirtualThreadExecutor.execute(() -> {\n        // print 1\n        log.info(\"userVirtualThreadExecutor : 1\");\n    });\n\n    userVirtualThreadExecutor.execute(() -> {\n        // print 2\n        log.info(\"userVirtualThreadExecutor : 2\");\n    });\n}\n\n@Test\npublic void getSimpleThreadExecutor() {\n    long userId = 1;\n\n    ThreadExecutor simpleThreadExecutor = ExecutorRegionKit.getSimpleThreadExecutor(userId);\n\n    simpleThreadExecutor.execute(() -> {\n        // print 1\n        log.info(\"simpleThreadExecutor : 1\");\n    });\n\n    simpleThreadExecutor.execute(() -> {\n        // print 2\n        log.info(\"simpleThreadExecutor : 2\");\n    });\n}\n```\n\n\n\n**从 FlowContext 中得到与用户（玩家）所关联的线程执行器**\n\n```java\nvoid executor() {\n    // 该方法具备全链路调用日志跟踪\n    flowContext.execute(() -> {\n        log.info(\"用户线程执行器\");\n    });\n\n    // 正常提交任务到用户线程执行器中\n    // getExecutor() 用户线程执行器\n    flowContext.getExecutor().execute(() -> {\n        log.info(\"用户线程执行器\");\n    });\n}\n\nvoid executeVirtual() {\n    // 该方法具备全链路调用日志跟踪\n    flowContext.executeVirtual(() -> {\n        log.info(\"用户虚拟线程执行器\");\n    });\n\n    // 正常提交任务到用户虚拟线程执行器中\n    // getVirtualExecutor() 用户虚拟线程执行器\n    flowContext.getVirtualExecutor().execute(() -> {\n        log.info(\"用户虚拟线程执行器\");\n    });\n\n    // 示例演示 - 更新元信息（可以使用虚拟线程执行完成一些耗时的操作）\n    flowContext.executeVirtual(() -> {\n        log.info(\"用户虚拟线程执行器\");\n        \n        // 更新元信息\n        flowContext.updateAttachment();\n        \n        // ... ... 其他业务逻辑\n    });\n}\n```\n\n\n\n##### 日志相关\n\n日志库使用新版本 slf4j 2.x\n\n\n\n[#230](https://github.com/game-town/ioGame/issues/230) 支持全链路调用日志跟踪；\n\n\n\n\n**开启 traceId 特性**\n\n该配置需要在游戏对外服中设置，因为游戏对外服是玩家请求的入口。\n\n```java\n// true 表示开启 traceId 特性\nIoGameGlobalConfig.openTraceId = true;\n```\n\n\n\n将全链路调用日志跟踪插件 TraceIdInOut 添加到业务框架中，表示该游戏逻辑服需要支持全链路调用日志跟踪。如果游戏逻辑服没有添加该插件的，表示不需要记录日志跟踪。\n\n```java\nBarSkeletonBuilder builder = ...;\n// traceId\nTraceIdInOut traceIdInOut = new TraceIdInOut();\nbuilder.addInOut(traceIdInOut);\n```\n\n\n\n##### 分布式事件总线 - 跨服解耦\n\n[#228](https://github.com/game-town/ioGame/issues/228) 分布式事件总线是新增的通讯方式，可以代替 redis pub sub 、 MQ ...等中间件产品；分布式事件总线具备全链路调用日志跟踪，这点是中间件产品所做不到的。\n\n\n\n**ioGame 分布式事件总线，特点**\n\n- 使用方式与 Guava EventBus 类似\n- 具备**全链路调用日志跟踪**。（这点是中间件产品做不到的）\n- 支持跨多个机器、多个进程通信\n- 支持与多种不同类型的多个逻辑服通信\n- 纯 javaSE，不依赖其他服务，耦合性低。（不需要安装任何中间件）\n- 事件源和事件监听器之间通过事件进行通信，从而实现了模块之间的解耦\n- 当没有任何远程订阅者时，**将不会触发网络请求**。（这点是中间件产品做不到的）\n\n\n\n下面两个订阅者是分别在**不同的进程中**的，当事件发布后，这两个订阅者都能接收到 UserLoginEventMessage 消息。\n\n```java\n@ActionController(UserCmd.cmd)\npublic class UserAction {\n\t... 省略部分代码\n    @ActionMethod(UserCmd.fireEvent)\n    public String fireEventUser(FlowContext flowContext) {\n        long userId = flowContext.getUserId();\n\n        log.info(\"fire : {} \", userId);\n        \n        // 事件源\n        var userLoginEventMessage = new UserLoginEventMessage(userId);\n        // 发布事件\n        flowContext.fire(userLoginEventMessage);\n\n        return \"fireEventUser\";\n    }\n}\n\n// 该订阅者在 【UserLogicStartup 逻辑服】进程中，与 UserAction 同在一个进程\n@EventBusSubscriber\npublic class UserEventBusSubscriber {\n    @EventSubscribe(ExecutorSelector.userExecutor)\n    public void userLogin(UserLoginEventMessage message) {\n        log.info(\"event - 玩家[{}]登录，记录登录时间\", message.getUserId());\n    }\n}\n\n// 该订阅者在 【EmailLogicStartup 逻辑服】进程中。\n@EventBusSubscriber\npublic class EmailEventBusSubscriber {\n    @EventSubscribe\n    public void mail(UserLoginEventMessage message) {\n        long userId = message.getUserId();\n        log.info(\"event - 玩家[{}]登录，发放 email 奖励\", userId);\n    }\n}\n```\n\n\n\n##### 小结\n\n在 ioGame21 中，该版本做了数百项优化及史诗级增强。\n\n- 在线文档方面\n- 线程管理域方面的开放与统一、减少线程池上下文切换\n- FlowContext 增强\n- 新增通讯方式 - 分布式事件总线\n- 游戏对外服方面增强\n- 全链路调用日志跟踪\n\n\n\n#### ioGame17 迁移到 ioGame21\n\n文档：[17 迁移到 ioGame21](https://www.yuque.com/iohao/game/hcgsfobyoph9r74r) \n\n\n\n#### ioGame17 - 更新日志\n\nsee online [ioGame17 - 更新日志](https://iohao.github.io/game/docs/archive/version_log_17) "
  },
  {
    "path": "common/README.md",
    "content": "```text\n.\n├── common-core 业务框架\n└── common-kit 工具相关\n\n```"
  },
  {
    "path": "common/common-core/README.md",
    "content": "## 业务框架\n\n如果说  [sofa-bolt](https://www.sofastack.tech/projects/sofa-bolt/overview/) 是为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上。而业务框架正是解决业务逻辑如何方便实现这一问题上。业务框架是游戏框架的一部分，职责是简化程序员的业务逻辑实现，业务框架使程序员能够快速的开始编写游戏业务。\n\n\n\n业务框架对于每个 action （即业务的处理类） 都是通过 [asm](https://www.oschina.net/p/reflectasm) 与 Singleton、Flyweight 、Command 等设计模式结合，对 action 的获取上通过 array 来得到，是一种近原生的方式。\n\n\n\n业务框架平均每秒可以执行 1152 万次业务逻辑。\n\n"
  },
  {
    "path": "common/common-core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>common-core</artifactId>\n    <name>common-core for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>common-kit</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>common-validation</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->\n        <dependency>\n            <groupId>com.alibaba.fastjson2</groupId>\n            <artifactId>fastjson2</artifactId>\n            <version>${fastjson.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/org.fusesource.jansi/jansi -->\n        <dependency>\n            <groupId>org.fusesource.jansi</groupId>\n            <artifactId>jansi</artifactId>\n            <version>${jansi.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/com.thoughtworks.qdox/qdox -->\n        <dependency>\n            <groupId>com.thoughtworks.qdox</groupId>\n            <artifactId>qdox</artifactId>\n            <version>${qdox.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>jakarta.validation</groupId>\n            <artifactId>jakarta.validation-api</artifactId>\n            <version>${jakarta.validation-api.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-context</artifactId>\n            <version>${spring.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>light-jprotobuf</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->\n        <dependency>\n            <groupId>org.hibernate.validator</groupId>\n            <artifactId>hibernate-validator</artifactId>\n            <version>${hibernate-validator.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/org.glassfish/jakarta.el -->\n        <!-- EL实现。在 Java SE 环境中，您必须将实现作为依赖项添加到 POM 文件中 -->\n        <dependency>\n            <groupId>org.glassfish</groupId>\n            <artifactId>jakarta.el</artifactId>\n            <version>${jakarta.el.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n\n</project>"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/IoGameVersion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton;\n\n/**\n * @author 渔民小镇\n * @date 2022-12-23\n */\npublic final class IoGameVersion {\n    public static final String VERSION;\n\n    static {\n        String internalVersion = \"<version>21.34</version>\";\n\n        VERSION = internalVersion\n                .replace(\"<version>\", \"\")\n                .replace(\"</version>\", \"\")\n        ;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/ActionController.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 控制器注解, 一般用作类的路由\n * <pre>\n *     类 cmd 注解\n *     一般用作类的路由\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface ActionController {\n    /**\n     * @return 路由\n     */\n    int value();\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/ActionMethod.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 方法注解, 一般用作类方法的路由\n * <pre>\n *     方法 subCmd 注解\n *     一般用作类方法的路由\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface ActionMethod {\n    /**\n     * subCmd int\n     *\n     * @return subCmd\n     */\n    int value();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/DocActionSend.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.annotation;\n\nimport com.iohao.game.action.skeleton.core.doc.BroadcastDocument;\n\nimport java.lang.annotation.*;\n\n/**\n * 文档相关\n * <pre>\n *     仅用于推送文档生成\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-31\n * @deprecated 请使用 {@link BroadcastDocument} 代替\n */\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Repeatable(DocActionSends.class)\n@Deprecated\npublic @interface DocActionSend {\n    /** 主路由 */\n    int cmd();\n\n    /** 子路由 */\n    int subCmd();\n\n    /** 业务对象class */\n    Class<?> dataClass();\n\n    /** 推送描述 */\n    String description() default \"\";\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/DocActionSends.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.annotation;\n\nimport com.iohao.game.action.skeleton.core.doc.BroadcastDocument;\n\nimport java.lang.annotation.*;\n\n/**\n * 文档相关\n * <pre>\n *     仅用于推送文档生成\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-31\n * @deprecated 请使用 {@link BroadcastDocument} 代替\n */\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Deprecated\npublic @interface DocActionSends {\n    DocActionSend[] value();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/ValidatedGroup.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 验证组\n *\n * @author fangwei\n * @date 2022-09-20\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})\npublic @interface ValidatedGroup {\n    /**\n     * 确定验证组，校验组对象的 Class\n     *\n     * @return 校验组对象的 Class 数组\n     */\n    Class<?>[] value() default {};\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 注解相关\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.annotation;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommand.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.esotericsoftware.reflectasm.ConstructorAccess;\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport com.iohao.game.action.skeleton.annotation.ValidatedGroup;\nimport com.iohao.game.action.skeleton.core.doc.ActionCommandDoc;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.parser.MethodParser;\nimport com.iohao.game.action.skeleton.core.flow.parser.MethodParsers;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\nimport java.lang.reflect.ParameterizedType;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n/**\n * ActionCommand 命令对象，也称为 action。\n * <pre>\n *     在业务框架构建时阶段创建的， ActionCommand 中保存了 action 相关信息，如：\n *     路由信息\n *     类信息\n *     类访问器\n *     方法对象\n *     方法名\n *     方法访问器\n *     方法信息下标\n *     方法返回值类型\n *     类是否托管于容器管理（是否交付给容器来管理 如 spring 等）\n * </pre>\n *\n * <pre>\n *     作用：\n *     业务框架处理业务流程时，也就是处理时阶段。会通过 action 来得到想要的各种信息。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@Getter\npublic final class ActionCommand {\n    /** cmdInfo */\n    final CmdInfo cmdInfo;\n\n    /** 一个single控制器对象 */\n    final Object actionController;\n    /** 方法所在 class */\n    final Class<?> actionControllerClazz;\n    /** 默认:true ，action 对象是 single. 如果设置为 false, 每次创建新的 action 类的对象. */\n    final boolean createSingleActionCommandController;\n\n    /** 方法对象 */\n    final Method actionMethod;\n    /** 方法名 */\n    final String actionMethodName;\n    /** 方法下标 */\n    final int actionMethodIndex;\n    /** 方法访问器 */\n    final MethodAccess actionMethodAccess;\n\n    /** 方法参数信息 数组 */\n    final ParamInfo[] paramInfos;\n    /** 方法是否有参数: true 有参数 */\n    final boolean methodHasParam;\n    /** 方法是否有异常抛出, 一般是错误码: true 有异常 */\n    final boolean throwException;\n    /** 返回类型 */\n    final ActionMethodReturnInfo actionMethodReturnInfo;\n\n    final ActionCommandDoc actionCommandDoc;\n\n    /** true 表示交付给容器来管理 如 spring 等 */\n    boolean deliveryContainer;\n    /** 构造方法访问器 */\n    ConstructorAccess<?> actionControllerConstructorAccess;\n\n    private ActionCommand(Builder builder) {\n        // -------------- 路由相关 --------------\n        this.cmdInfo = CmdInfo.of(builder.cmd, builder.subCmd);\n\n        // -------------- 控制器相关 --------------\n        this.actionControllerClazz = builder.actionControllerClazz;\n        this.actionController = builder.actionController;\n        this.createSingleActionCommandController = builder.createSingleActionCommandController;\n\n        // -------------- 控制器-方法相关 --------------\n        this.actionMethod = builder.actionMethod;\n        this.actionMethodName = builder.actionMethodName;\n        this.actionMethodIndex = builder.actionMethodIndex;\n        this.actionMethodAccess = builder.actionMethodAccess;\n\n        // -------------- 控制器-方法参数相关 --------------\n        this.paramInfos = builder.paramInfos;\n        this.methodHasParam = builder.paramInfos != null;\n        this.throwException = builder.actionMethod.getExceptionTypes().length != 0;\n        this.actionMethodReturnInfo = new ActionMethodReturnInfo(builder);\n\n        this.actionCommandDoc = builder.actionCommandDoc;\n\n        this.deliveryContainer = builder.deliveryContainer;\n    }\n\n    public Stream<ParamInfo> streamParamInfo() {\n        return this.methodHasParam ? Arrays.stream(this.paramInfos) : Stream.empty();\n    }\n\n    /**\n     * action method 是否包含相关注解\n     *\n     * @param annotationClass 相关注解\n     * @return true 是的\n     * @since 21.10\n     */\n    public boolean containAnnotation(Class<? extends Annotation> annotationClass) {\n        return this.getAnnotation(annotationClass) != null;\n    }\n\n    /**\n     * 获取 action method 相关注解\n     *\n     * @param annotationClass 相关注解\n     * @param <T>             t\n     * @return 相关注解\n     * @since 21.10\n     */\n    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {\n        return actionMethod.getAnnotation(annotationClass);\n    }\n\n    /**\n     * {@link ActionCommand} 命令的构建器\n     * <p>\n     * 因为 {@link ActionCommand} 的参数较复杂, 所以这里考虑用构建器。\n     */\n    @Accessors(chain = true)\n    @Setter\n    @FieldDefaults(level = AccessLevel.PUBLIC)\n    static final class Builder {\n        /** 目标路由 */\n        int cmd;\n        /** 目标子路由 */\n        int subCmd;\n        /** 方法访问器 */\n        MethodAccess actionMethodAccess;\n        /** 方法名 */\n        String actionMethodName;\n        /** tcp controller类 */\n        Class<?> actionControllerClazz;\n        /** 方法对象 */\n        Method actionMethod;\n        /** 参数列表信息 */\n        ActionCommand.ParamInfo[] paramInfos;\n        /** 方法下标 (配合 MethodAccess 使用) */\n        int actionMethodIndex;\n        /** 返回值信息 */\n        Class<?> returnTypeClazz;\n        /** action command 文档 */\n        ActionCommandDoc actionCommandDoc;\n        /** true 表示交付给容器来管理 如 spring 等 */\n        boolean deliveryContainer;\n        /** 默认:true ，action 对象是 single. 如果设置为 false, 每次创建新的 action 类的对象. */\n        boolean createSingleActionCommandController;\n        /** 一个single控制器对象 */\n        Object actionController;\n\n        ActionCommand build() {\n            return new ActionCommand(this);\n        }\n    }\n\n    /**\n     * action 方法中的参数与返回值信息\n     */\n    public interface MethodParamResultInfo {\n        /**\n         * 是否是 List 类型\n         *\n         * @return true 是 List 类型\n         */\n        default boolean isList() {\n            return false;\n        }\n\n        /**\n         * List 泛型的类型，也称为方法返回值类型\n         * <pre>\n         *     如果不是方法的返回值不是 List 类型，这个值会取自 paramClazz 成员属性\n         *     原计划想用 Collection ，这样可以兼容 Set 之类的；但似乎这样有一点争议，先暂支持 List 把\n         * </pre>\n         *\n         * @return List 泛型的类型\n         */\n        Class<?> getActualTypeArgumentClazz();\n    }\n\n    /**\n     * 方法参数信息\n     */\n    @Getter\n    public static final class ParamInfo implements MethodParamResultInfo {\n        /** JSR380 空验证组 */\n        static final Class<?>[] EMPTY_GROUPS = new Class<?>[0];\n        /** 参数名 */\n        final String name;\n        /** 参数下标 */\n        final int index;\n        /** 保存 Parameter 对象 */\n        final Parameter parameter;\n        /** 参数类型 */\n        final Class<?> paramClazz;\n        /**\n         * List 泛型的类型，也称为方法返回值类型\n         * <pre>\n         *     如果方法的返回值不是 List 类型，这个值会取自 paramClazz 成员属性\n         *     原计划想用 Collection ，这样可以兼容 Set 之类的；但似乎这样有一点争议，先暂支持 List\n         * </pre>\n         */\n        final Class<?> actualTypeArgumentClazz;\n        /** true 是 list 类型 */\n        final boolean list;\n\n        /**\n         * 实际的内置包装类型\n         * <pre>\n         *     常规的 java class 是指本身，如：\n         *     开发者自定义了一个 StudentPb，在 action 方法上参数声明为 xxx(StudentPb studentPb)\n         *     那么这个值就是 StudentPb\n         * </pre>\n         *\n         * <pre>\n         *     但由于框架现在内置了一些包装类型，如：\n         *     int --> IntValue\n         *     List&lt;Integer&gt; --> IntValueList\n         *\n         *     long --> LongValue\n         *     List&lt;Long&gt; --> LongValueList\n         *\n         *     所以当开发者在 action 方法上参数声明为基础类型时；\n         *     如声明为 int 那么这个值将会是 IntValue\n         *     如声明为 long 那么这个值将会是 LongValue\n         *\n         *     如声明为 List&lt;Integer&gt; 那么这个值将会是 IntValueList\n         *     包装类型相关的以此类推;\n         *\n         *     这么做的目的是为了生成文档时，不与前端产生争议，\n         *     如果提供给前端的文档显示 int ，或许前端同学会懵B，\n         *     当然如果提前与前端同学沟通好这些约定，也不是那么麻烦。\n         *     但实际上如果前端是新来接手项目的，碰见这种情况也会小懵，\n         *     所以为了避免这些小尬，框架在生成文档时，用基础类型的内置包装类型来表示。\n         * </pre>\n         */\n        final Class<?> actualClazz;\n        final boolean customMethodParser;\n        /** JSR380 验证组 */\n        final Class<?>[] validatorGroups;\n        /** true 表示参数类型是 {@link FlowContext} */\n        final boolean flowContext;\n        /** true : 开启 JSR380 验证规范 */\n        boolean validator;\n\n        ParamInfo(int index, Parameter p) {\n            // 保存Parameter对象\n            this.parameter = p;\n            // 方法的参数下标\n            this.index = index;\n            // 方法的参数名\n            this.name = p.getName();\n            // 方法的参数类型 class\n            this.paramClazz = p.getType();\n\n            /*\n             * 关于方法参数支持 list 这个特性也纠结了很久\n             * 因为这可能会给开发者造成一些困惑，现在方法支持 list 但只是为了支持基础类型相关的 list\n             * 开发者会不会把这个 list 的泛型类型用在协议上，如: List<StudentPb> 这种；\n             */\n            if (List.class.isAssignableFrom(this.paramClazz)) {\n                ParameterizedType genericReturnType = (ParameterizedType) p.getParameterizedType();\n                this.actualTypeArgumentClazz = (Class<?>) genericReturnType.getActualTypeArguments()[0];\n                this.list = true;\n            } else {\n                this.actualTypeArgumentClazz = this.paramClazz;\n                this.list = false;\n            }\n\n            MethodParser methodParser = MethodParsers.getMethodParser(this);\n            this.actualClazz = methodParser.getActualClazz(this);\n            this.customMethodParser = methodParser.isCustomMethodParser();\n\n            // JSR380 相关，确定验证组，校验组对象的 Class 数组\n            var validatedAnn = this.parameter.getAnnotation(ValidatedGroup.class);\n            this.validatorGroups = Objects.isNull(validatedAnn) ? EMPTY_GROUPS : validatedAnn.value();\n\n            this.flowContext = FlowContext.class.isAssignableFrom(actualTypeArgumentClazz);\n        }\n\n        /**\n         * 是否业务参数\n         *\n         * @return true 业务参数\n         * @since 21.7\n         */\n        public boolean isBizData() {\n            return !this.flowContext;\n        }\n\n        @Override\n        public String toString() {\n            return this.toString(false);\n        }\n\n        public String toString(boolean fullName) {\n            boolean isCustomList = this.list && !MethodParsers.containsKey(this.actualClazz);\n\n            if (isCustomList) {\n                String simpleNameParamClazz = this.paramClazz.getSimpleName();\n                String simpleNameActualClazz = fullName\n                        ? this.actualClazz.getName()\n                        : this.actualClazz.getSimpleName();\n\n                return String.format(\"%s<%s> %s\", simpleNameParamClazz, simpleNameActualClazz, this.name);\n            }\n\n            String simpleNameActualClazz = fullName\n                    ? this.actualClazz.getName()\n                    : this.actualClazz.getSimpleName();\n\n            return String.format(\"%s %s\", simpleNameActualClazz, this.name);\n        }\n    }\n\n    /**\n     * action 方法参数返回值信息\n     */\n    @Getter\n    @FieldDefaults(level = AccessLevel.PRIVATE)\n    public static final class ActionMethodReturnInfo implements MethodParamResultInfo {\n        /** 返回类型 */\n        final Class<?> returnTypeClazz;\n        /**\n         * List 泛型的类型，也称为方法返回值类型\n         * <pre>\n         *     如果不是方法的返回值不是 List 类型，这个值会取自 returnTypeClazz 成员属性\n         *     原计划想用 Collection ，这样可以兼容 Set 之类的；但似乎这样有一点争议，先暂支持 List 把\n         * </pre>\n         */\n        final Class<?> actualTypeArgumentClazz;\n        /** true 是 list 类型 */\n        final boolean list;\n        /**\n         * 实际的内置包装类型\n         * <pre>\n         *     常规的 java class 是指本身，如：\n         *     开发者自定义了一个 StudentPb，在 action 方法上参数声明为 xxx(StudentPb studentPb)\n         *     那么这个值就是 StudentPb\n         * </pre>\n         *\n         * <pre>\n         *     但由于框架现在内置了一些包装类型，如：\n         *     int --> IntValue\n         *     List&lt;Integer&gt; --> IntValueList\n         *\n         *     long --> LongValue\n         *     List&lt;Long&gt; --> LongValueList\n         *\n         *     所以当开发者在 action 方法上参数声明为基础类型时；\n         *     如声明为 int 那么这个值将会是 IntValue\n         *     如声明为 long 那么这个值将会是 LongValue\n         *\n         *     如声明为 List&lt;Integer&gt; 那么这个值将会是 IntValueList\n         *     包装类型相关的以此类推;\n         *\n         *     这么做的目的是为了生成文档时，不与前端产生争议，\n         *     如果提供给前端的文档显示 int ，或许前端同学会懵B，\n         *     当然如果提前与前端同学沟通好这些约定，也不是那么麻烦。\n         *     但实际上如果前端是新来接手项目的，碰见这种情况也会小懵，\n         *     所以为了避免这些小尬，框架在生成文档时，用基础类型的内置包装类型来表示。\n         * </pre>\n         */\n        final Class<?> actualClazz;\n        final boolean customMethodParser;\n\n        private ActionMethodReturnInfo(ActionCommand.Builder builder) {\n\n            this.returnTypeClazz = builder.returnTypeClazz;\n\n            if (List.class.isAssignableFrom(returnTypeClazz)) {\n                ParameterizedType genericReturnType = (ParameterizedType) builder.actionMethod.getGenericReturnType();\n                this.actualTypeArgumentClazz = (Class<?>) genericReturnType.getActualTypeArguments()[0];\n                this.list = true;\n            } else {\n                this.actualTypeArgumentClazz = returnTypeClazz;\n                this.list = false;\n            }\n\n            MethodParser methodParser = MethodParsers.getMethodParser(this);\n            this.actualClazz = methodParser.getActualClazz(this);\n            this.customMethodParser = methodParser.isCustomMethodParser();\n        }\n\n        /**\n         * 方法返回值类型是否 void\n         *\n         * @return true 是 void\n         */\n        public boolean isVoid() {\n            return Void.TYPE == this.returnTypeClazz;\n        }\n\n        @Override\n        public String toString() {\n            return toString(false);\n        }\n\n        public String toString(boolean fullName) {\n            boolean isCustomList = this.list && !MethodParsers.containsKey(this.actualClazz);\n\n            if (isCustomList) {\n                String simpleNameReturnTypeClazz = this.returnTypeClazz.getSimpleName();\n                String simpleNameActualClazz = fullName\n                        ? this.actualClazz.getName()\n                        : this.actualClazz.getSimpleName();\n\n                return String.format(\"%s<%s>\", simpleNameReturnTypeClazz, simpleNameActualClazz);\n            }\n\n            return fullName\n                    ? this.actualClazz.getName()\n                    : this.actualClazz.getSimpleName();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandDocParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\nimport com.iohao.game.action.skeleton.core.doc.*;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.lang.reflect.Method;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * source doc 解析类\n *\n * @author 渔民小镇\n * @date 2022-12-09\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class ActionCommandDocParser {\n    final ActionCommandParser actionCommandParser;\n\n    final Map<Integer, ActionCommandDoc> actionCommandDocMap = new NonBlockingHashMap<>();\n\n    final ActionCommandDoc emptyActionCommandDoc = new ActionCommandDoc();\n\n    ActionCommandDocParser(ActionCommandParser actionCommandParser, List<Class<?>> controllerList, boolean parseDoc) {\n        this.actionCommandParser = actionCommandParser;\n        if (parseDoc) {\n            this.buildSourceDoc(controllerList);\n        }\n    }\n\n    private void buildSourceDoc(List<Class<?>> controllerList) {\n\n        var actionCommandRegions = this.actionCommandParser.getActionCommandRegions();\n\n        // java source\n        Map<String, JavaClassDocInfo> javaClassDocInfoMap = ActionCommandDocKit.getJavaClassDocInfoMap(controllerList);\n\n        this.actionCommandParser.getActionControllerStream(controllerList).parallel().forEach(controllerClazz -> {\n            // java source\n            JavaClassDocInfo javaClassDocInfo = javaClassDocInfoMap.get(controllerClazz.toString());\n\n            // 主路由 (类上的路由)\n            int cmd = controllerClazz.getAnnotation(ActionController.class).value();\n\n            // 过期的方法，将来需要删除的部分\n            extractedDeprecate(actionCommandRegions, controllerClazz, javaClassDocInfo, cmd);\n\n            // action 文档\n            ActionDoc actionDoc = IoGameDocumentHelper.ofActionDoc(cmd, controllerClazz);\n            actionDoc.setJavaClassDocInfo(javaClassDocInfo);\n\n            this.actionCommandParser.getMethodStream(controllerClazz).forEach(method -> {\n                ActionCommandDoc actionCommandDoc = getActionCommandDoc(javaClassDocInfo, method);\n                // 目标子路由 (方法上的路由)\n                int subCmd = method.getAnnotation(ActionMethod.class).value();\n\n                int cmdMerge = CmdKit.merge(cmd, subCmd);\n                // 将数据保存在这里。\n                actionCommandDocMap.put(cmdMerge, actionCommandDoc);\n                actionDoc.addActionCommandDoc(actionCommandDoc);\n            });\n        });\n    }\n\n    private static void extractedDeprecate(ActionCommandRegions actionCommandRegions, Class<?> controllerClazz, JavaClassDocInfo javaClassDocInfo, int cmd) {\n        var actionCommandRegion = actionCommandRegions.getActionCommandRegion(cmd);\n        actionCommandRegion.setActionControllerClazz(controllerClazz);\n        actionCommandRegion.setJavaClassDocInfo(javaClassDocInfo);\n    }\n\n    ActionCommandDoc getActionCommandDoc(int cmd, int subCmd) {\n        int cmdMerge = CmdKit.merge(cmd, subCmd);\n        return actionCommandDocMap.getOrDefault(cmdMerge, emptyActionCommandDoc);\n    }\n\n    private ActionCommandDoc getActionCommandDoc(JavaClassDocInfo javaClassDocInfo, Method method) {\n\n        if (javaClassDocInfo != null) {\n            return javaClassDocInfo.createActionCommandDoc(method);\n        }\n\n        return new ActionCommandDoc();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandFlowExecute.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\n\n/**\n * 命令流程执行器\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface ActionCommandFlowExecute {\n    /**\n     * 模板方法模式：\n     * <pre>\n     * 在一个方法中定义一个算法的骨架，而将一些步骤延迟到子类中。\n     * 模板方法使得子类可以在不改变算法结构的情况下，重新定义算法中的某些步骤。\n     * 要点：\n     * - “模板方法”定义了算法的步骤，把这些步骤的实现延迟到子类。\n     * - 模板方法模式为我们提供了一种代码复用的重要技巧。\n     * - 模板方法的抽象类可以定义具体方法、抽象方法和钩子。\n     * - 抽象方法由子类实现。\n     * - 钩子是一种方法，它在抽象类中不做事，或者只做默认的事情，子类可以选择要不要去覆盖它。\n     * - 为了防止子类改变模板方法中的算法，可以将模板方法声明为final。\n     * - 好莱坞原则告诉我们，将决策权放在高层模块中，以便决定如何以及何时调用低层模块。\n     * - 你将在真实世界代码中看到模板方法模式的许多变体，不要期待它们全都是一眼就可以被你一眼认出的。\n     * - 策略模式和模板方法模式都封装算法，一个用组合，一个用继承。\n     * - 工厂方法是模板方法的一种特殊版本。\n     * </pre>\n     *\n     * <pre>\n     * 命令的执行入口, 类似模板方法模式, 但却并不是真正的模板方法模式\n     * 这里在处理请求\n     * 提供了用户可配置的多个 (执行前的钩子, 执行后的钩子)\n     * </pre>\n     *\n     * @param flowContext FlowContext\n     */\n    void execute(FlowContext flowContext);\n\n    static ActionCommandFlowExecute defaultInstance() {\n        return DefaultActionCommandFlowExecute.me();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContextKit;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 该handler用于执行 {@link ActionCommand} 对象\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@Slf4j\nclass ActionCommandHandler implements Handler {\n\n    @Override\n    public boolean handler(final FlowContext flowContext) {\n\n        try {\n            // 设置 flowContext 的一些属性\n            this.settingFlowContext(flowContext);\n            // actionCommand 命令流程执行器\n            ActionCommandFlowExecute.defaultInstance().execute(flowContext);\n        } catch (Throwable e) {\n            log.error(e.getMessage(), e);\n            return false;\n        }\n\n        return true;\n    }\n\n    protected void settingFlowContext(FlowContext flowContext) {\n        /*\n         * 做一些兼容，这部分逻辑在 RequestMessageClientProcessor 已经设置过一次，\n         * 这里做兼容的目的有两个：\n         * 1 防止开发者将 RequestMessageClientProcessor 移除，并做了相关的自定义。\n         * 2 业务框架可以单独做测试\n         */\n        FlowContextKit.employ(flowContext);\n\n        // 业务框架\n        BarSkeleton barSkeleton = flowContext.getBarSkeleton();\n\n        // 参数解析器\n        var paramParser = barSkeleton.getActionMethodParamParser();\n        // 得到业务方法的参数列表，并验证\n        var params = paramParser.listParam(flowContext);\n        // 业务方法参数 save to flowContext\n        flowContext.setMethodParams(params);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\n\nimport com.esotericsoftware.reflectasm.ConstructorAccess;\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\nimport com.iohao.game.action.skeleton.core.action.parser.ActionParserContext;\nimport com.iohao.game.action.skeleton.core.action.parser.ActionParserListener;\nimport com.iohao.game.action.skeleton.core.codec.ProtoDataCodec;\nimport com.iohao.game.action.skeleton.core.doc.ActionCommandDoc;\nimport com.iohao.game.action.skeleton.core.doc.ActionDoc;\nimport com.iohao.game.action.skeleton.core.doc.IoGameDocumentHelper;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.Parameter;\nimport java.util.*;\nimport java.util.stream.Stream;\n\n/**\n * action 命令对象解析器，将 action 类下的业务方法解析为 actionCommand\n * <pre>\n *     解析后的 actionCommand 将存放到对应的 ActionCommandRegions 中\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class ActionCommandParser {\n    /** 命令域 管理器 */\n    @Getter\n    final ActionCommandRegions actionCommandRegions = new ActionCommandRegions();\n\n    final BarSkeletonSetting setting;\n    final ActionParserListeners actionParserListeners;\n    BarSkeleton barSkeleton;\n\n    ActionCommandParser(BarSkeletonBuilder builder) {\n        this.setting = builder.getSetting();\n        this.actionParserListeners = builder.actionParserListeners;\n    }\n\n    /**\n     * 构建映射\n     * <pre>\n     *     这个方法可以做一个抽象接口\n     *     让接口子类来处理如何生成 ActionCommand 的数据\n     *     但现在不着急; 2021-12-21\n     * </pre>\n     *\n     * @param controllerList action 类的 class 列表\n     */\n    ActionCommandParser buildAction(List<Class<?>> controllerList) {\n        var doc = new ActionCommandDocParser(this, controllerList, setting.parseDoc);\n\n        // action 类的 stream\n        this.getActionControllerStream(controllerList).forEach(controllerClazz -> {\n            // 方法访问器: 获取类中自己定义的方法\n            var methodAccess = MethodAccess.get(controllerClazz);\n\n            // 主路由 (类上的路由)\n            int cmd = controllerClazz.getAnnotation(ActionController.class).value();\n            // 子路由 map\n            var actionCommandRegion = this.actionCommandRegions.getActionCommandRegion(cmd);\n            actionCommandRegion.setActionControllerClazz(controllerClazz);\n\n            // true 表示交付给容器来管理 如 spring 等\n            boolean deliveryContainer = this.deliveryContainer(controllerClazz);\n            // action 类的实例化对象\n            Object actionControllerInstance = ofActionInstance(controllerClazz);\n\n            // 遍历所有方法上有 ActionMethod 注解的方法对象\n            this.getMethodStream(controllerClazz).forEach(method -> {\n                // 相同的 action 方法名只允许存在一个，建议与路由同名。\n                checkSoleActionName(method, actionCommandRegion, doc);\n\n                // 目标子路由 (方法上的路由)\n                int subCmd = method.getAnnotation(ActionMethod.class).value();\n                // 方法名\n                String methodName = method.getName();\n                // 方法下标\n                Class<?>[] parameterTypes = method.getParameterTypes();\n                int methodIndex = methodAccess.getIndex(methodName, parameterTypes);\n                // 方法返回值类型\n                Class<?> returnType = methodAccess.getReturnTypes()[methodIndex];\n                // source doc\n                ActionCommandDoc actionCommandDoc = doc.getActionCommandDoc(cmd, subCmd);\n\n                // 新建一个命令构建器\n                var builder = new ActionCommand.Builder()\n                        .setCmd(cmd)\n                        .setSubCmd(subCmd)\n                        .setActionControllerClazz(controllerClazz)\n                        .setActionMethod(method)\n                        .setActionMethodName(methodName)\n                        .setActionMethodIndex(methodIndex)\n                        .setActionMethodAccess(methodAccess)\n                        .setReturnTypeClazz(returnType)\n                        .setActionCommandDoc(actionCommandDoc)\n                        .setDeliveryContainer(deliveryContainer)\n                        .setCreateSingleActionCommandController(this.setting.createSingleActionCommandController)\n                        .setActionController(actionControllerInstance);\n\n                // 检测路由是否重复\n                checkExistSubCmd(controllerClazz, subCmd, actionCommandRegion);\n\n                // 方法参数信息\n                paramInfo(method, builder);\n\n                /*\n                 * 路由 key，根据这个路由可以找到对应的 command（命令对象）\n                 * 将映射类的方法，保存在 command 中，每个 command 封装成一个命令对象。\n                 */\n                var command = builder.build();\n\n                // 子路由映射\n                actionCommandRegion.add(command);\n\n                // 文档相关\n                ActionDoc actionDoc = IoGameDocumentHelper.ofActionDoc(cmd, controllerClazz);\n                actionDoc.addActionCommand(command);\n            });\n        });\n\n        // 内部将所有的 action 转换为 action 二维数组\n        actionCommandRegions.initActionCommandArray(setting);\n        // action 构建时的监听\n        executeActionListeners();\n\n        return this;\n    }\n\n    Stream<Class<?>> getActionControllerStream(List<Class<?>> controllerList) {\n        Set<Class<?>> controllerSet = new HashSet<>(controllerList);\n        // action 类的 stream\n        return controllerSet.stream()\n                // 条件: 类上配置了 ActionController 注解\n                .filter(clazz -> Objects.nonNull(clazz.getAnnotation(ActionController.class)));\n    }\n\n    /**\n     * 得到 action 类中标准 action 的 method stream\n     * <pre>\n     *     返回方法上带有 ActionMethod 的方法对象\n     *     术语解释：在 action 类中提供的业务方法通常称为 action\n     *\n     *     当前标准 action 映射规则\n     *     1. 业务方法上添加注解 ActionMethod\n     *     2. 业务方法的访问权限必须是：public\n     *     3. 业务方法不能是：static\n     *     4. 业务方法需要是在 action 类中声明的方法\n     *     简单的说，标准的 action 是非静态的，且访问权限为 public 的方法。\n     *     术语说明：在 action 类中提供的业务方法通常称为 action。\n     *\n     *     其他访问权限方法是 ioGame 业务框架中保留使用方式，\n     *     比如将来有可能将声明为 private 的业务方法，即 private action ，\n     *     私有 action 只能是内部逻辑服访问的。\n     *     可以简单的理解为 private action 是给逻辑服之间提供访问的，\n     *     这样开发者可以不需要在游戏对外服中做访问权限的控制。\n     *     但这样可能会给开发者带来使用上的混淆，所以短期内不会提供这样的使用方式；\n     * </pre>\n     *\n     * @param actionControllerClass 类\n     * @return 标准 action 方法对象 Stream\n     */\n    Stream<Method> getMethodStream(Class<?> actionControllerClass) {\n        return Arrays\n                // 得到 action 类的所有方法\n                .stream(actionControllerClass.getDeclaredMethods())\n                // 得到在业务方法上添加了 ActionMethod 注解的方法对象\n                .filter(method -> Objects.nonNull(method.getAnnotation(ActionMethod.class)))\n                // 访问权限必须是 public 的\n                .filter(method -> Modifier.isPublic(method.getModifiers()))\n                // 不能是静态方法的\n                .filter(method -> !Modifier.isStatic(method.getModifiers()));\n    }\n\n    private boolean deliveryContainer(Class<?> controllerClazz) {\n\n        if (DependencyInjectionPart.me().isInjection()) {\n            return DependencyInjectionPart.me().deliveryContainer(controllerClazz);\n        }\n\n        return false;\n    }\n\n    private void paramInfo(Method method, ActionCommand.Builder builder) {\n\n        // 方法参数列表\n        var parameters = method.getParameters();\n        if (Objects.isNull(parameters)) {\n            return;\n        }\n\n        var paramInfos = new ActionCommand.ParamInfo[parameters.length];\n        builder.setParamInfos(paramInfos);\n\n        for (int i = 0; i < parameters.length; i++) {\n            // 方法的参数对象\n            Parameter parameter = parameters[i];\n            // 构建参数信息\n            var paramInfo = new ActionCommand.ParamInfo(i, parameter);\n            paramInfos[i] = paramInfo;\n\n            /*\n             * 下面是 JSR380 相关的逻辑\n             *\n             * 1 没开启 JSR380 验证， 不做处理\n             * 2 过滤不需要验证的参数\n             */\n            if (!this.setting.validator || paramInfo.isFlowContext()) {\n                continue;\n            }\n\n            paramInfo.validator = ValidatorKit.isValidator(parameter.getType());\n        }\n    }\n\n    private void checkExistSubCmd(Class<?> controllerClass, int subCmd, ActionCommandRegion actionCommandRegion) {\n\n        if (actionCommandRegion.containsKey(subCmd)) {\n\n            String message = StrKit.format(\"cmd:【{}】下已经存在方法编号 subCmd:【{}】 .请查看: {}\",\n                    actionCommandRegion.cmd,\n                    subCmd,\n                    controllerClass);\n\n            IoGameBanner.me().ofRuntimeException(message);\n        }\n    }\n\n    private void checkSoleActionName(Method method,\n                                     ActionCommandRegion actionCommandRegion,\n                                     ActionCommandDocParser doc) {\n\n        for (ActionCommand command : actionCommandRegion.values()) {\n\n            if (Objects.equals(method.getName(), command.actionMethodName)) {\n                int cmd = actionCommandRegion.cmd;\n                // 目标子路由 (方法上的路由)\n                int subCmd = method.getAnnotation(ActionMethod.class).value();\n                ActionCommandDoc actionCommandDoc = doc.getActionCommandDoc(cmd, subCmd);\n\n                String template = \"\"\"\n                        [action 方法重名] 同一个类中相同的 action 方法名只允许存在一个，建议与路由同名。\n                            see-1 : %s - %s.%s(%s.java:%d)\n                            see-2 : %s - %s.%s(%s.java:%d)\"\"\";\n\n                Class<?> actionControllerClazz = actionCommandRegion.getActionControllerClazz();\n                String simpleName = actionControllerClazz.getSimpleName();\n\n                var message = template.formatted(\n                        CmdInfo.of(cmd, subCmd), actionControllerClazz, method.getName()\n                        , simpleName, actionCommandDoc.getLineNumber(),\n\n                        command.getCmdInfo(), actionControllerClazz, method.getName()\n                        , simpleName, command.getActionCommandDoc().getLineNumber()\n                );\n\n                IoGameBanner.me().ofRuntimeException(message);\n            }\n        }\n    }\n\n    private Object ofActionInstance(Class<?> controllerClazz) {\n        // 如果 actionController 交给容器管理了，就从容器中获取实例，否则就 newInstance\n        boolean deliveryContainer = this.deliveryContainer(controllerClazz);\n\n        var actionInstance = deliveryContainer\n                ? DependencyInjectionPart.me().getBean(controllerClazz)\n                : ConstructorAccess.get(controllerClazz).newInstance();\n\n        // 正常来说不会为 null，除非是开发者在集成其他容器时，没有实现 getBean 方法\n        Objects.requireNonNull(actionInstance);\n\n        return actionInstance;\n    }\n\n    private void executeActionListeners() {\n        actionCommandRegions.regionMap.forEach((cmd, actionCommandRegion) -> {\n            // action command, actionMethod\n            actionCommandRegion.getSubActionCommandMap().forEach((subCmd, command) -> {\n                // action 构建时的上下文\n                var context = new ActionParserContext()\n                        .setBarSkeleton(barSkeleton)\n                        .setActionCommand(command);\n\n                // action 构建时的监听 - actionCommand\n                this.actionParserListeners.onActionCommand(context);\n            });\n        });\n\n        this.actionParserListeners.onAfter(barSkeleton);\n    }\n}\n\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class ActionParserListeners {\n    final Map<Class<?>, ActionParserListener> map = new NonBlockingHashMap<>();\n\n    void addActionParserListener(ActionParserListener listener) {\n        Objects.requireNonNull(listener);\n        this.map.putIfAbsent(listener.getClass(), listener);\n    }\n\n    void onActionCommand(ActionParserContext context) {\n        this.map.forEach((clazz, listener) -> listener.onActionCommand(context));\n    }\n\n    void onAfter(BarSkeleton barSkeleton) {\n        this.map.forEach((clazz, listener) -> listener.onAfter(barSkeleton));\n    }\n\n    ActionParserListeners() {\n        // 监听器 - 预先创建协议代理类\n        if (DataCodecKit.getDataCodec() instanceof ProtoDataCodec) {\n            this.addActionParserListener(new ProtobufActionParserListener());\n            this.addActionParserListener(new ProtobufCheckActionParserListener());\n        }\n\n        // prepared actionControllerConstructorAccess\n        this.addActionParserListener(context -> {\n            var actionCommand = context.getActionCommand();\n            if (!actionCommand.isDeliveryContainer()) {\n                actionCommand.actionControllerConstructorAccess = ConstructorAccess.get(actionCommand.actionControllerClazz);\n            }\n        });\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.doc.JavaClassDocInfo;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Collection;\nimport java.util.Map;\n\n/**\n * ActionCommand 域，通常与 ActionController 是 1:1 的关系\n *\n * <pre>\n *     类似模块的区分，这样可以避免 map 嵌 map 的结构\n *     在代码的阅读上也会清晰很多\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic final class ActionCommandRegion {\n    final int cmd;\n    /** actionControllerClazz */\n    Class<?> actionControllerClazz;\n    /** actionControllerClazz 的源文件信息 */\n    JavaClassDocInfo javaClassDocInfo;\n    /**\n     * <pre>\n     *     key: subCmd\n     *     value: ActionCommand\n     * </pre>\n     */\n    Map<Integer, ActionCommand> subActionCommandMap = new NonBlockingHashMap<>();\n\n    public ActionCommandRegion(int cmd) {\n        this.cmd = cmd;\n    }\n\n    public boolean containsKey(int subCmd) {\n        return this.subActionCommandMap.containsKey(subCmd);\n    }\n\n    public void add(ActionCommand subActionCommand) {\n        var cmdInfo = subActionCommand.getCmdInfo();\n\n        int subCmd = cmdInfo.getSubCmd();\n\n        this.subActionCommandMap.put(subCmd, subActionCommand);\n    }\n\n    /**\n     * 得到子路由最大值\n     *\n     * @return 子路由最大值\n     */\n    public int getMaxSubCmd() {\n        return subActionCommandMap\n                .keySet()\n                .stream()\n                .max(Integer::compareTo)\n                .orElse(0)\n                ;\n    }\n\n    public Collection<ActionCommand> values() {\n        return this.subActionCommandMap.values();\n    }\n\n    /**\n     * 将子路由列表转为数组\n     *\n     * @return array\n     */\n    public ActionCommand[] arrayActionCommand() {\n        // 子路由最大值\n        int subCmdMax = this.getMaxSubCmd() + 1;\n        ActionCommand[] subBehaviors = new ActionCommand[subCmdMax];\n\n        for (Map.Entry<Integer, ActionCommand> subEntry : subActionCommandMap.entrySet()) {\n            subBehaviors[subEntry.getKey()] = subEntry.getValue();\n        }\n\n        return subBehaviors;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandRegionGlobalCheckKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 全局重复路由检测工具\n * <pre>\n *     实际上，如果是按照 COC 原则的项目结构，是不需要这个工具的。\n *     这个工具主要是对多个业务框架中，加载相同的 action 进行检查。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-07-31\n */\n@UtilityClass\npublic class ActionCommandRegionGlobalCheckKit {\n\n    Map<String, ActionCommandRegions> map = new HashMap<>();\n\n    public void putActionCommandRegions(String key, ActionCommandRegions actionCommandRegions) {\n\n        if (map.containsKey(key)) {\n            return;\n        }\n\n        map.put(key, actionCommandRegions);\n    }\n\n    /**\n     * 全局重复路由检测\n     */\n    public void checkGlobalExistSubCmd() {\n\n        Map<Integer, ActionCommand> cmdMap = new HashMap<>(100);\n\n        // 多服单进程下的所有业务框架的命令域管理器\n        var actionCommandRegionList = map\n                .values()\n                .parallelStream()\n                .flatMap(ActionCommandRegions::streamActionCommandRegion)\n                .toList();\n\n        for (ActionCommandRegion actionCommandRegion : actionCommandRegionList) {\n            // 命令域下的路由 action\n            for (ActionCommand actionCommand : actionCommandRegion.values()) {\n                // 路由信息\n                CmdInfo cmdInfo = actionCommand.getCmdInfo();\n\n                int cmdMerge = cmdInfo.getCmdMerge();\n                // 如果是重复路由，就抛异常\n                if (cmdMap.containsKey(cmdMerge)) {\n\n                    String template = \"\"\"\n                            全局重复路由检测，使用了相同的路由，或者多个业务框架中，加载了相同的 action\n                            cmd:【{}】下已经存在方法编号 subCmd:【{}】 .请查看: {}\n                            \"\"\";\n\n                    String message = StrKit.format(template,\n                            actionCommandRegion.cmd,\n                            cmdInfo.getSubCmd(),\n                            actionCommand.getActionControllerClazz()\n                    );\n\n                    ThrowKit.ofRuntimeException(message);\n                }\n\n                cmdMap.put(cmdMerge, actionCommand);\n            }\n        }\n\n        // 清空数据，检测完了\n        map.clear();\n        cmdMap.clear();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandRegions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.MoreKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * 命令域 管理器\n * <pre>\n *     管理命令域\n *\n *     路由与子路由的关系维护\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic final class ActionCommandRegions {\n    private static final ActionCommand[][] EMPTY = new ActionCommand[0][0];\n\n    /**\n     * action map\n     * <pre>\n     *     key : cmd\n     *     value : subCmd region\n     * </pre>\n     */\n    final Map<Integer, ActionCommandRegion> regionMap = new NonBlockingHashMap<>();\n\n    /**\n     * action 数组. 下标对应 cmd\n     * <pre>\n     *     第一维: cmd\n     *     第二维: cmd 下面的子 subCmd\n     *\n     *     这里使用数组，没有使用 map 嵌入 map 的方式\n     *     因为 map 嵌 map 的方式获取一个 action 需要多次 hash 和 equals 才能找到\n     *     而通过数组则可以快速的找到对应的 action\n     * </pre>\n     */\n    ActionCommand[][] actionCommands = EMPTY;\n\n    /**\n     * 获取命令处理器\n     *\n     * @param cmd    路由\n     * @param subCmd 子路由\n     * @return 命令处理器\n     */\n    public ActionCommand getActionCommand(int cmd, int subCmd) {\n\n        if (cmd >= actionCommands.length) {\n            return null;\n        }\n\n        var subActionCommands = actionCommands[cmd];\n\n        if (subCmd >= subActionCommands.length) {\n            return null;\n        }\n\n        return actionCommands[cmd][subCmd];\n    }\n\n    /**\n     * 根据合并路由 cmdMerge 得到 ActionCommand\n     *\n     * @param cmdMerge 合并路由\n     * @return ActionCommand\n     */\n    public ActionCommand getActionCommand(int cmdMerge) {\n        var cmd = CmdKit.getCmd(cmdMerge);\n        var subCmd = CmdKit.getSubCmd(cmdMerge);\n        return this.getActionCommand(cmd, subCmd);\n    }\n\n    /**\n     * 命令列表\n     *\n     * @return cmdMerge\n     */\n    public List<Integer> listCmdMerge() {\n        return regionMap.values()\n                // 并发流\n                .parallelStream()\n                // 将 map.values 合并成一个 list\n                .flatMap((Function<ActionCommandRegion, Stream<ActionCommand>>) actionCommandRegion -> actionCommandRegion.values().parallelStream())\n                // 转为命令路由信息\n                .map(ActionCommand::getCmdInfo)\n                // 转为 合并的路由\n                .map(CmdInfo::getCmdMerge)\n                .collect(Collectors.toList())\n                ;\n    }\n\n    Stream<ActionCommandRegion> streamActionCommandRegion() {\n        return this.regionMap.values().parallelStream();\n    }\n\n    ActionCommandRegion getActionCommandRegion(int cmd) {\n\n        var actionCommandRegion = this.regionMap.get(cmd);\n\n        // 无锁化\n        if (Objects.isNull(actionCommandRegion)) {\n            var newValue = new ActionCommandRegion(cmd);\n            return MoreKit.putIfAbsent(this.regionMap, cmd, newValue);\n        }\n\n        return actionCommandRegion;\n    }\n\n    void initActionCommandArray(BarSkeletonSetting barSkeletonSetting) {\n        this.actionCommands = this.convertArray(barSkeletonSetting);\n    }\n\n    /**\n     * 将 map 转换成二维数组\n     * <pre>\n     *     第一维: cmd\n     *     第二维: cmd 下面的子 subCmd\n     * </pre>\n     *\n     * @param barSkeletonSetting config\n     * @return 二维数组\n     */\n    private ActionCommand[][] convertArray(BarSkeletonSetting barSkeletonSetting) {\n\n        if (this.regionMap.isEmpty()) {\n            return EMPTY;\n        }\n\n        // 获取主路由最大值\n        int max = getMaxCmd(barSkeletonSetting);\n\n        var behaviors = new ActionCommand[max][1];\n\n        this.regionMap.keySet().forEach(cmd -> {\n            var actionCommandRegion = this.regionMap.get(cmd);\n\n            behaviors[cmd] = actionCommandRegion.arrayActionCommand();\n        });\n\n        return behaviors;\n    }\n\n    private int getMaxCmd(BarSkeletonSetting barSkeletonSetting) {\n        // 获取最大的路由数字 并且+1\n        int max = this.regionMap\n                          .keySet()\n                          .stream()\n                          .max(Integer::compareTo)\n                          .orElse(0) + 1;\n\n        if (max > barSkeletonSetting.getCmdMaxLen()) {\n            /*\n             * %s exceeds the maximum default value.\n             * Please set the capacity manually if necessary.\n             * Default maximum capacity %d, current capacity %d\n             */\n            var info = Bundle.getMessage(MessageKey.cmdMergeLimit).formatted(\n                    \"cmd\", barSkeletonSetting.getCmdMaxLen(), max\n            );\n\n            IoGameBanner.me().ofRuntimeException(info);\n        }\n\n        // subCmd\n        for (ActionCommandRegion actionCommandRegion : this.regionMap.values()) {\n\n            int subCmdMax = actionCommandRegion.getMaxSubCmd() + 1;\n\n            if (subCmdMax > barSkeletonSetting.getSubCmdMaxLen()) {\n                var info = Bundle.getMessage(MessageKey.cmdMergeLimit).formatted(\n                        \"subCmd\", barSkeletonSetting.getSubCmdMaxLen(), subCmdMax\n                );\n\n                IoGameBanner.me().ofRuntimeException(info);\n            }\n        }\n\n        return max;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionFactoryBean.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\n\n/**\n * action 类对象创建工厂，负责创建 action 类的实例化对象\n *\n * @param <T> t\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface ActionFactoryBean<T> {\n    /**\n     * 获取 action 类的对象\n     * <pre>\n     *     添加了 {@link ActionController} 注解的类，是一个 action 类\n     * </pre>\n     *\n     * @param actionCommand actionCommand\n     * @return action 类的实例化对象\n     */\n    T getBean(ActionCommand actionCommand);\n\n    /**\n     * 通过 action class 得到对应 bean\n     *\n     * @param actionControllerClazz action class\n     * @return action 类的实例化对象\n     */\n    default T getBean(Class<?> actionControllerClazz) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionParserListenerAbout.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.action.skeleton.core.action.parser.ActionParserContext;\nimport com.iohao.game.action.skeleton.core.action.parser.ActionParserListener;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.action.skeleton.protocol.wrapper.*;\nimport com.iohao.game.common.kit.ProtoKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Predicate;\n\n/**\n * Prepared action proto\n *\n * @author 渔民小镇\n * @date 2024-05-01\n * @since 21.7\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class ProtobufActionParserListener implements ActionParserListener {\n    static final Set<Class<?>> protoSet = new NonBlockingHashSet<>();\n\n    static {\n        // create a protobuf proxy class\n        ProtoKit.create(ByteValueList.class);\n\n        ProtoKit.create(IntValue.class);\n        ProtoKit.create(IntValueList.class);\n\n        ProtoKit.create(BoolValue.class);\n        ProtoKit.create(BoolValueList.class);\n\n        ProtoKit.create(LongValue.class);\n        ProtoKit.create(LongValueList.class);\n\n        ProtoKit.create(StringValue.class);\n        ProtoKit.create(StringValueList.class);\n    }\n\n    @Override\n    public void onActionCommand(ActionParserContext context) {\n        // 添加了 ProtobufClass 注解的类\n        Predicate<Class<?>> protobufClassPredicate = c -> Objects.nonNull(c.getAnnotation(ProtobufClass.class));\n        collect(context, protobufClassPredicate, protoSet);\n    }\n\n    static void collect(ActionParserContext context, Predicate<Class<?>> protobufClassPredicate, Set<Class<?>> protoSet) {\n        // 将 action 的方法参数与返回值添加了 ProtobufClass 注解的类信息收集到 protoSet 中\n        ActionCommand actionCommand = context.getActionCommand();\n        // action 参数相关\n        actionCommand.streamParamInfo()\n                // 只处理业务参数\n                .filter(ActionCommand.ParamInfo::isBizData)\n                // 得到参数类型\n                .map(ActionCommand.ParamInfo::getActualTypeArgumentClazz)\n                // 协议碎片类型不做处理\n                .filter(clazz -> !WrapperKit.isWrapper(clazz))\n                // 添加了 ProtobufClass 注解的类\n                .filter(protobufClassPredicate)\n                .forEach(protoSet::add);\n\n        // action 返回值相关\n        Optional\n                .ofNullable(actionCommand.getActionMethodReturnInfo())\n                // void 不处理\n                .filter(actionMethodReturnInfo -> !actionMethodReturnInfo.isVoid())\n                .map(ActionCommand.ActionMethodReturnInfo::getActualTypeArgumentClazz)\n                // 协议碎片类型不做处理\n                .filter(clazz -> !WrapperKit.isWrapper(clazz))\n                // 添加了 ProtobufClass 注解的类\n                .filter(protobufClassPredicate)\n                .ifPresent(protoSet::add);\n    }\n\n    @Override\n    public void onAfter(BarSkeleton barSkeleton) {\n        protoSet.forEach(ProtoKit::create);\n    }\n}\n\n/**\n * proto 协议类型添检测\n *\n * @author 渔民小镇\n * @date 2024-05-02\n * @since 21.7\n */\n@Slf4j\nfinal class ProtobufCheckActionParserListener implements ActionParserListener {\n    static final Set<Class<?>> protoSet = new NonBlockingHashSet<>();\n\n    @Override\n    public void onActionCommand(ActionParserContext context) {\n        // 添加了 ProtobufClass 注解的类\n        Predicate<Class<?>> protobufClassPredicate = c -> c.getAnnotation(ProtobufClass.class) == null;\n        ProtobufActionParserListener.collect(context, protobufClassPredicate, protoSet);\n    }\n\n    @Override\n    public void onAfter(BarSkeleton barSkeleton) {\n        if (protoSet.isEmpty()) {\n            return;\n        }\n\n        log.error(Bundle.getMessage(MessageKey.protobufAnnotationCheck));\n        for (Class<?> protoClass : protoSet) {\n            log.error(protoClass.toString());\n        }\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionSend.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-30\n */\n@Deprecated\npublic interface ActionSend {\n\n    /**\n     * 合并路由\n     *\n     * @return 合并路由\n     * @see CmdKit#merge(int, int)\n     */\n    int getCmdMerge();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarMessageKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\n\n/**\n * 创建 RequestMessage，ResponseMessage 相关内部消息的工具类\n *\n * @author 渔民小镇\n * @date 2022-06-07\n * @see RequestMessage\n * @see ResponseMessage\n */\n@UtilityClass\npublic class BarMessageKit {\n    public RequestMessage createRequestMessage(CmdInfo cmdInfo) {\n        return createRequestMessage(cmdInfo, null);\n    }\n\n    /**\n     * 创建 RequestMessage\n     *\n     * @param cmdInfo 路由\n     * @param data    业务数据\n     * @return RequestMessage\n     */\n    public RequestMessage createRequestMessage(CmdInfo cmdInfo, Object data) {\n\n        RequestMessage requestMessage = new RequestMessage();\n\n        employ(requestMessage, cmdInfo, data);\n\n        return requestMessage;\n    }\n\n    /**\n     * 将路由、业务数据设置到 RequestMessage 中\n     *\n     * @param requestMessage RequestMessage\n     * @param cmdInfo        路由\n     * @param data           业务数据\n     */\n    public void employ(RequestMessage requestMessage, CmdInfo cmdInfo, Object data) {\n\n        var headMetadata = new HeadMetadata()\n                .setCmdInfo(cmdInfo)\n                // 请求命令类型: 0 心跳，1 业务; see ExternalMessageCmdCode\n                .setCmdCode(1);\n\n        requestMessage.setHeadMetadata(headMetadata);\n\n        if (Objects.nonNull(data)) {\n            requestMessage.setData(data);\n        }\n    }\n\n    /**\n     * 创建响应对象\n     *\n     * @param cmdInfo 路由地址\n     * @return ResponseMessage\n     */\n    public ResponseMessage createResponseMessage(CmdInfo cmdInfo) {\n\n        ResponseMessage responseMessage = new ResponseMessage();\n        // 元信息 路由地址\n        responseMessage.setHeadMetadata(new HeadMetadata().setCmdInfo(cmdInfo));\n\n        return responseMessage;\n    }\n\n    /**\n     * 创建响应对象\n     *\n     * @param cmdInfo 路由地址\n     * @param bizData 业务数据\n     * @return ResponseMessage\n     */\n    public ResponseMessage createResponseMessage(CmdInfo cmdInfo, Object bizData) {\n\n        Objects.requireNonNull(bizData);\n\n        ResponseMessage responseMessage = createResponseMessage(cmdInfo);\n        // 业务数据\n        responseMessage.setData(bizData);\n\n        return responseMessage;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeleton.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.*;\nimport com.iohao.game.action.skeleton.core.runner.Runners;\nimport com.iohao.game.common.kit.attr.AttrOptionDynamic;\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\n\n\n/**\n * 整个核心的骨架.积木骷髅 (业务框架)\n * <pre>\n *     ta可以处理所有来访者 {@link Handler}, 你可以为ta添加多种处理链,直到你满意为止.\n *     ta可以拿起笔和本子 {@link ActionMethodInOut}, 记录所看到的东西.\n *     处理完后你会让ta如何收尾 {@link ActionAfter}\n *\n *     发挥你的想象力\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n * @see SkeletonAttr 业务框架动态属性\n */\n@Getter\n@Accessors(chain = true)\n@Setter(AccessLevel.PACKAGE)\npublic final class BarSkeleton implements AttrOptionDynamic {\n    final AttrOptions options = new AttrOptions();\n    /** handler array */\n    final Handler[] handlers;\n    Runners runners;\n    /** 命令域 管理器 */\n    ActionCommandRegions actionCommandRegions = new ActionCommandRegions();\n    /** InOut 插件相关 */\n    InOutManager inOutManager;\n    /** action 对象创建工厂 */\n    ActionFactoryBean<Object> actionFactoryBean;\n    /** InvokeActionMethod */\n    ActionMethodInvoke actionMethodInvoke;\n    /** 方法参数解析器 */\n    ActionMethodParamParser actionMethodParamParser;\n    /** 异常处理 */\n    ActionMethodExceptionProcess actionMethodExceptionProcess;\n    /** 结果包装器 */\n    ActionMethodResultWrap actionMethodResultWrap;\n    /** action 执行完后，最后需要做的事。 一般用于将数据发送到 Broker（游戏网关） */\n    ActionAfter actionAfter;\n    /** 响应对象的创建 */\n    ResponseMessageCreate responseMessageCreate;\n    /** 业务框架 flow 上下文 工厂 */\n    FlowContextFactory flowContextFactory;\n    /** 与业务框架所关联的线程执行器管理域 */\n    ExecutorRegion executorRegion;\n\n    BarSkeleton(Handler[] handlers) {\n        this.handlers = handlers;\n    }\n\n    public static BarSkeletonBuilder newBuilder() {\n        return new BarSkeletonBuilder();\n    }\n\n    /**\n     * 业务框架处理入口\n     *\n     * @param flowContext flowContext\n     */\n    public void handle(final FlowContext flowContext) {\n        // 将业务框架设置到 FlowContext 中\n        flowContext.setBarSkeleton(this);\n\n        /*\n         * 多次访问的变量，保存到局部变量，可以提升性能。\n         * 把成员变量的访问变为局部变量的访问 。 通过栈帧访问（线程栈），不用每次从堆中得到成员变量\n         *\n         * 因为这段代码访问频繁，才这样做。常规下不需要这么做\n         * 可以参考 HashMap 的 putVal 方法相关\n         */\n        var handlers = this.handlers;\n\n        if (handlers.length == 1) {\n            handlers[0].handler(flowContext);\n            return;\n        }\n\n        for (Handler theHandler : handlers) {\n            if (!theHandler.handler(flowContext)) {\n                return;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeletonBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.doc.*;\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport com.iohao.game.action.skeleton.core.flow.*;\nimport com.iohao.game.action.skeleton.core.flow.internal.*;\nimport com.iohao.game.action.skeleton.core.action.parser.ActionParserListener;\nimport com.iohao.game.action.skeleton.core.runner.Runner;\nimport com.iohao.game.action.skeleton.core.runner.Runners;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.concurrent.executor.*;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\n\nimport java.util.*;\n\n/**\n * 骨架构建器\n * <pre>\n *     关于业务框架的构建器可以参考这里：\n *\n *     <a href=\"https://iohao.github.io/game/docs/core/framework\">文档-业务框架的构建器</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@Setter\n@Accessors(chain = true)\npublic final class BarSkeletonBuilder {\n    /** BarSkeletonSetting */\n    @Getter\n    final BarSkeletonSetting setting = new BarSkeletonSetting();\n    final Runners runners = new Runners();\n    /** handler 列表 */\n    final List<Handler> handlerList = new LinkedList<>();\n    /** action class */\n    final List<Class<?>> actionControllerClazzList = new LinkedList<>();\n    /** 错误码 */\n    @Deprecated\n    final List<MsgExceptionInfo> msgExceptionInfoList = new ArrayList<>();\n    /** action 构建时的钩子方法 */\n    @Setter(AccessLevel.PRIVATE)\n    ActionParserListeners actionParserListeners = new ActionParserListeners();\n    /** action工厂 */\n    ActionFactoryBean<Object> actionFactoryBean = new DefaultActionFactoryBean<>();\n    /** action 执行完后，最后需要做的事。 一般用于将数据发送到 Broker（游戏网关） */\n    ActionAfter actionAfter = new DefaultActionAfter();\n    /** 结果包装器 */\n    ActionMethodResultWrap actionMethodResultWrap = new DefaultActionMethodResultWrap();\n    /** 异常处理 */\n    ActionMethodExceptionProcess actionMethodExceptionProcess = new DefaultActionMethodExceptionProcess();\n    /** InvokeActionMethod */\n    ActionMethodInvoke actionMethodInvoke = new DefaultActionMethodInvoke();\n    /** ActionMethod 方法参数解析器 */\n    ActionMethodParamParser actionMethodParamParser = new DefaultActionMethodParamParser();\n    /** 响应对象的创建 */\n    ResponseMessageCreate responseMessageCreate = new DefaultResponseMessageCreate();\n    /** 业务框架 flow 上下文 工厂 */\n    FlowContextFactory flowContextFactory = FlowContext::new;\n    /** 线程执行器 */\n    ExecutorRegion executorRegion;\n    /** InOut 插件相管理器，ActionCommand 执行前与执行后的逻辑钩子类 */\n    InOutManager inOutManager = InOutManager.ofPipeline();\n\n    BarSkeletonBuilder() {\n    }\n\n    /**\n     * 构建骨架, 提供了一些默认配置\n     */\n    public BarSkeleton build() {\n\n        // 设置一些默认值\n        this.defaultSetting();\n\n        // 业务框架处理器\n        var handlers = new Handler[this.handlerList.size()];\n        this.handlerList.toArray(handlers);\n\n        // 业务框架参数设置\n        var barSkeleton = new BarSkeleton(handlers)\n                // action 工厂\n                .setActionFactoryBean(this.actionFactoryBean)\n                // ActionMethod Invoke 方法回调\n                .setActionMethodInvoke(this.actionMethodInvoke)\n                // ActionMethod 方法参数解析器\n                .setActionMethodParamParser(this.actionMethodParamParser)\n                // ActionMethod 的异常处理\n                .setActionMethodExceptionProcess(this.actionMethodExceptionProcess)\n                // ActionMethod 结果包装器\n                .setActionMethodResultWrap(this.actionMethodResultWrap)\n                // action after， action 执行完后，最后需要做的事。 一般用于将数据发送到 Broker（游戏网关）\n                .setActionAfter(this.actionAfter)\n                // 响应对象的创建\n                .setResponseMessageCreate(this.responseMessageCreate)\n                // 业务框架 flow 上下文 工厂\n                .setFlowContextFactory(this.flowContextFactory)\n                // 线程执行器\n                .setExecutorRegion(this.executorRegion)\n                // runners 机制\n                .setRunners(this.runners)\n                // inout\n                .setInOutManager(this.inOutManager);\n\n        // 构建 actionMapping\n        this.extractedActionCommand(barSkeleton);\n\n        // 控制台打印\n        PrintActionKit.print(barSkeleton, this.setting);\n\n        // 文档相关\n        IoGameDocumentHelper.setGenerateDoc(this.setting.generateDoc);\n\n        this.runners.setBarSkeleton(barSkeleton);\n\n        this.actionParserListeners = null;\n\n        if (IoGameBanner.troublemaker) {\n            IoGameBanner.troubleCounter++;\n        }\n\n        return barSkeleton;\n    }\n\n    public BarSkeletonBuilder addActionController(Class<?> controller) {\n        Objects.requireNonNull(controller);\n        this.actionControllerClazzList.add(controller);\n        return this;\n    }\n\n    /**\n     * 添加广播文档\n     *\n     * @param builder 广播文档构建器\n     * @return this\n     */\n    public BarSkeletonBuilder addBroadcastDocument(BroadcastDocumentBuilder builder) {\n        IoGameDocumentHelper.addBroadcastDocument(builder.build());\n        return this;\n    }\n\n    public BarSkeletonBuilder addHandler(Handler handler) {\n        Objects.requireNonNull(handler);\n        // 先进先执行\n        this.handlerList.add(handler);\n        return this;\n    }\n\n    /**\n     * 添加 inOut\n     * <pre>\n     *     如果存在相同的类型，则覆盖之前的\n     * </pre>\n     *\n     * @param inOut inOut\n     * @return this\n     */\n    public BarSkeletonBuilder addInOut(ActionMethodInOut inOut) {\n        Objects.requireNonNull(inOut);\n        this.inOutManager.addInOut(inOut);\n        return this;\n    }\n\n    /**\n     * 添加 Runner 机制，会在逻辑服与 Broker（游戏网关）建立连接之前（onStart）、之后（onStartAfter）分别触发一次。\n     *\n     * @param runner Runner\n     * @return this\n     */\n    public BarSkeletonBuilder addRunner(Runner runner) {\n        this.runners.addRunner(runner);\n        return this;\n    }\n\n    public BarSkeletonBuilder addActionParserListener(ActionParserListener listener) {\n        this.actionParserListeners.addActionParserListener(listener);\n        return this;\n    }\n\n    private void extractedActionCommand(BarSkeleton barSkeleton) {\n        // action 命令对象解析器\n        var actionCommandParser = new ActionCommandParser(this)\n                .setBarSkeleton(barSkeleton)\n                // 根据 action 类列表，来构建 ActionCommand\n                .buildAction(this.actionControllerClazzList);\n\n        var actionCommandRegions = actionCommandParser.getActionCommandRegions();\n\n        // 将 ActionCommandRegions 命令域管理器，保存到业务框架中\n        barSkeleton.setActionCommandRegions(actionCommandRegions);\n    }\n\n    private void defaultSetting() {\n        // 如果没有配置 handler，那么使用默认的\n        if (this.handlerList.isEmpty()) {\n            this.handlerList.add(new ActionCommandHandler());\n        }\n\n        // 创建线程执行器\n        if (Objects.isNull(this.executorRegion)) {\n            this.executorRegion = ExecutorRegionKit.createExecutorRegion();\n        }\n    }\n\n    /**\n     * addMsgExceptionInfo\n     *\n     * @param msgExceptionInfo msgExceptionInfo\n     * @return BarSkeletonBuilder\n     * @deprecated 请使用 {@link IoGameDocumentHelper#addErrorCodeClass(Class)} 代替\n     */\n    @Deprecated\n    public BarSkeletonBuilder addMsgExceptionInfo(MsgExceptionInfo msgExceptionInfo) {\n//        Objects.requireNonNull(msgExceptionInfo);\n//        this.errorCodeDocs.addMsgExceptionInfo(msgExceptionInfo);\n        return this;\n    }\n\n    /**\n     * addActionSend\n     *\n     * @param actionSend actionSend\n     * @return BarSkeletonBuilder\n     * @deprecated 请使用 {@link BarSkeletonBuilder#addBroadcastDoc(BroadcastDocBuilder)} 代替\n     */\n    @Deprecated\n    public BarSkeletonBuilder addActionSend(Class<?> actionSend) {\n//        Objects.requireNonNull(actionSend);\n//        this.actionSendClazzList.add(actionSend);\n        return this;\n    }\n\n    /**\n     * addActionSendDoc\n     *\n     * @param actionSendDoc actionSendDoc\n     * @return BarSkeletonBuilder\n     * @deprecated 请使用 {@link BarSkeletonBuilder#addBroadcastDoc(BroadcastDocBuilder)} 代替\n     */\n    @Deprecated\n    public BarSkeletonBuilder addActionSendDoc(ActionSendDoc actionSendDoc) {\n//        this.actionSendDocs.add(actionSendDoc);\n        return this;\n    }\n\n    /**\n     * 添加广播文档\n     *\n     * @param broadcastDocBuilder broadcastDocBuilder\n     * @return this\n     * @deprecated 请使用 {@link BarSkeletonBuilder#addBroadcastDocument(BroadcastDocumentBuilder)}\n     */\n    @Deprecated\n    public BarSkeletonBuilder addBroadcastDoc(BroadcastDocBuilder broadcastDocBuilder) {\n        IoGameDocumentHelper.addBroadcastDocument(broadcastDocBuilder.buildDocument());\n        return this;\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeletonBuilderParamConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.core.doc.BroadcastDocBuilder;\nimport com.iohao.game.action.skeleton.core.enhance.BarSkeletonBuilderEnhances;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.ClassScanner;\nimport com.iohao.game.action.skeleton.core.doc.IoGameDocumentHelper;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\n/**\n * BarSkeletonBuilderParamConfig 构建参数的配置\n * <pre>\n *     设置一些参数\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-02-01\n */\n@Setter\n@Getter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class BarSkeletonBuilderParamConfig {\n    /** action controller class. class has @ActionController */\n    final List<Class<?>> actionControllerClassList = new ArrayList<>();\n    /** action send class. class has @DocActionSend */\n    final List<Class<?>> actionSendClassList = new ArrayList<>();\n    /** 错误码 class */\n    @Deprecated\n    final List<MsgExceptionInfo> msgExceptionInfoList = new ArrayList<>();\n\n    /** true 打印广播日志，默认不打印 */\n    boolean broadcastLog;\n\n    /** ActionController filter */\n    Predicate<Class<?>> actionControllerPredicate = clazz -> Objects.nonNull(clazz.getAnnotation(ActionController.class));\n    boolean enhance = true;\n\n    /**\n     * 创建业务框架构建器\n     *\n     * @return 业务框架构建器\n     */\n    public BarSkeletonBuilder createBuilder() {\n        // 错误码-用于文档的生成\n        this.addErrorCode(ActionErrorEnum.values());\n\n        // 业务框架构建器\n        BarSkeletonBuilder builder = BarSkeleton.newBuilder();\n        enhance(builder);\n\n        // action controller class. class has @ActionController\n        this.scanClassActionController(builder::addActionController);\n\n        // true 打印广播日志，默认不打印\n        IoGameCommonCoreConfig.broadcastLog = this.broadcastLog;\n\n        extracted2();\n\n        return builder;\n    }\n\n    /**\n     * 扫描 action 类所在包\n     * <pre>\n     *     内部会扫描当前 acton 类的路径和子包路径下的所有类\n     *     类需要是 @ActionController 注解的\n     * </pre>\n     *\n     * @param actionControllerClass action 类\n     * @return this\n     */\n    public BarSkeletonBuilderParamConfig scanActionPackage(Class<?> actionControllerClass) {\n        this.actionControllerClassList.add(actionControllerClass);\n        return this;\n    }\n\n    /**\n     * 扫描 action 推送类所在包，用于推送文档的生成\n     * <pre>\n     *     内部会扫描当前 action 推送类的路径和子包路径下的所有类\n     *     类需要是 @DocActionSends 注解的\n     * </pre>\n     *\n     * @param actionSendClass action 推送类\n     * @return this\n     */\n    public BarSkeletonBuilderParamConfig scanActionSendPackage(Class<?> actionSendClass) {\n        this.actionSendClassList.add(actionSendClass);\n        return this;\n    }\n\n    /**\n     * 错误码-用于文档的生成\n     *\n     * @param msgExceptionInfoArray msgExceptionInfoArray\n     * @return this\n     * @deprecated 请使用 {@link IoGameDocumentHelper#addErrorCodeClass(Class)}\n     */\n    @Deprecated\n    public BarSkeletonBuilderParamConfig addErrorCode(MsgExceptionInfo[] msgExceptionInfoArray) {\n        msgExceptionInfoList.addAll(Arrays.asList(msgExceptionInfoArray));\n        return this;\n    }\n\n    private void enhance(BarSkeletonBuilder builder) {\n        if (this.enhance) {\n            BarSkeletonBuilderEnhances.enhance(builder);\n        }\n    }\n\n    /**\n     * 扫描 actionControllerClassList 并把扫描好的类交给 actionConsumer 消费\n     *\n     * @param actionConsumer 消费者\n     */\n    private void scanClassActionController(Consumer<Class<?>> actionConsumer) {\n        // action send class. class has @DocActionSend\n        scanClass(this.actionControllerClassList, this.actionControllerPredicate, actionConsumer);\n    }\n\n    /**\n     * 扫描 actionSendClassList 并把扫描好的类交给 sendConsumer 消费\n     *\n     * @param sendConsumer 消费者\n     * @deprecated 请使用 {@link BarSkeletonBuilder#addBroadcastDoc(BroadcastDocBuilder)} 代替\n     */\n    @Deprecated\n    private void scanClassActionSend(Consumer<Class<?>> sendConsumer) {\n        // action controller class. class has @ActionController\n//        scanClass(this.actionSendClassList, this.actionSendPredicate, sendConsumer);\n    }\n\n    private void scanClass(final List<Class<?>> actionList\n            , final Predicate<Class<?>> predicateFilter\n            , final Consumer<Class<?>> actionConsumer) {\n\n        for (Class<?> actionClazz : actionList) {\n            // 扫描\n            String packagePath = actionClazz.getPackageName();\n            ClassScanner classScanner = new ClassScanner(packagePath, predicateFilter);\n            List<Class<?>> classList = classScanner.listScan();\n\n            // 将扫描好的 class 添加到业务框架中\n            classList.forEach(actionConsumer);\n        }\n    }\n\n    private static void extracted2() {\n        if (!Objects.equals(IoGameBanner.flag21, \"ioGame..21..\")) {\n            byte[] bytes = new byte[]{105, 111, 71, 97, 109, 101, 72, 111, 109, 101, 32, 104, 116, 116, 112, 58, 47, 47, 103, 97, 109, 101, 46, 105, 111, 104, 97, 111, 46, 99, 111, 109, 10, 103, 105, 116, 104, 117, 98, 32, 32, 32, 32, 32, 104, 116, 116, 112, 115, 58, 47, 47, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 105, 111, 104, 97, 111, 47, 105, 111, 71, 97, 109, 101, 10};\n            IoGameBanner.printlnMsg2(new String(bytes, StandardCharsets.UTF_8));\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeletonSetting.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n/**\n * 业务框架 Setting\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@Getter\n@Setter\npublic final class BarSkeletonSetting {\n    /**\n     * <pre>\n     *     true: action 对象是 single.\n     *     false: 每次都创建新的 action 对象.\n     * </pre>\n     */\n    boolean createSingleActionCommandController = true;\n\n    /** action 的默认长度 (一级 cmd; 主路由) */\n    int cmdMaxLen = 127;\n    /** 子 action 的默认长度 (二级 subCmd; 子路由) */\n    int subCmdMaxLen = 127;\n\n    /** false 关闭打印 */\n    boolean print = true;\n    /** true action 日志打印 */\n    boolean printAction = true;\n    /** false action 日志打印短名称(类、参数名、返回值) */\n    boolean printActionShort = true;\n    /** true inout 日志打印 */\n    boolean printInout = true;\n    /** true handler 日志打印 */\n    boolean printHandler = true;\n    /** true 编解码器日志打印 */\n    boolean printDataCodec = true;\n\n    /** true runners 日志打印 */\n    boolean printRunners = false;\n\n    /** inOut 的 in 。 true 开启 */\n    @Deprecated\n    boolean openIn = true;\n    /** inOut 的 out 。 true 开启 */\n    @Deprecated\n    boolean openOut = true;\n\n    /**\n     * true : 业务参数开启 JSR380 验证规范\n     *\n     * <pre>\n     *     关于启 JSR380 验证规范可以参考这里：\n     *     <a href=\"https://iohao.github.io/game/docs/core/jsr380\">文档 - JSR380</a>\n     * </pre>\n     * <p>\n     * 需要在你的项目 maven 中引入相关 pom\n     * <pre>\n     *         &lt;!-- hibernate validator -->\n     *         &lt;dependency>\n     *             &lt;groupId>org.hibernate.validator&lt;/groupId>\n     *             &lt;artifactId>hibernate-validator&lt;/artifactId>\n     *             &lt;version>7.0.4.Final&lt;/version>\n     *         &lt;/dependency>\n     *\n     *         &lt;!-- EL实现。在Java SE环境中，您必须将实现作为依赖项添加到POM文件中-->\n     *         &lt;dependency>\n     *             &lt;groupId>org.glassfish&lt;/groupId>\n     *             &lt;artifactId>jakarta.el&lt;/artifactId>\n     *             &lt;version>4.0.2&lt;/version>\n     *         &lt;/dependency>\n     *\n     *         &lt;!-- 验证器Maven依赖项 -->\n     *         &lt;dependency>\n     *             &lt;groupId>jakarta.validation&lt;/groupId>\n     *             &lt;artifactId>jakarta.validation-api&lt;/artifactId>\n     *             &lt;version>3.0.2&lt;/version>\n     *         &lt;/dependency>\n     * </pre>\n     */\n    boolean validator = false;\n    /** true 开启文档解析 */\n    boolean parseDoc = true;\n    /** true 生成文档 */\n    boolean generateDoc = true;\n\n    BarSkeletonSetting() {\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/CmdInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport lombok.Getter;\n\n/**\n * cmdInfo 命令路由信息\n * <pre>\n *     平常大部分框架使用一个 cmd 来约定协议\n *     这里使用cmd,subCmd是为了模块的划分清晰, 当然这样规划还有更多好处\n *\n *     cmdInfo 的创建可以通过：\n *     1 {@link CmdInfoFlyweightFactory} 来完成\n *     2 {@link CmdInfo#of(int, int)} 静态方法来完成\n *\n *     其他参考：\n *     <a href=\"https://iohao.github.io/game/docs/manual/cmd\">文档 - 路由信息</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@Getter\npublic final class CmdInfo {\n    /** 业务主路由 */\n    final int cmd;\n    /** 业务子路由 */\n    final int subCmd;\n\n    /**\n     * 合并两个参数,分别存放在 [高16 和 低16]\n     * <pre>\n     *     cmd - 高16\n     *     subCmd - 低16\n     *     例如 cmd = 600; subCmd = 700;\n     *     merge 的结果: 39322300\n     *     那么 cmdMerge 对应的二进制是: [0000 0010 0101 1000] [0000 0010 1011 1100]\n     * </pre>\n     */\n    final int cmdMerge;\n\n    CmdInfo(int cmdMerge) {\n        // -------------- 路由相关 --------------\n        this.cmd = CmdKit.getCmd(cmdMerge);\n        this.subCmd = CmdKit.getSubCmd(cmdMerge);\n        this.cmdMerge = cmdMerge;\n    }\n\n    /**\n     * 获取 cmdInfo\n     * <pre>\n     *     内部使用享元工厂来获取 cmdInfo\n     *\n     *     请使用 of 系列方法来代替此方法\n     * </pre>\n     *\n     * @param cmd    主路由\n     * @param subCmd 子路由\n     * @return 路由信息\n     */\n    public static CmdInfo getCmdInfo(int cmd, int subCmd) {\n        return of(cmd, subCmd);\n    }\n\n    /**\n     * 获取 cmdInfo\n     * <pre>\n     *     内部使用享元工厂来获取 cmdInfo\n     *\n     *     请使用 of 系列方法来代替此方法\n     * </pre>\n     *\n     * @param cmdMerge cmd-subCmd {@link CmdKit#merge(int, int)}\n     * @return 路由信息\n     */\n    public static CmdInfo getCmdInfo(int cmdMerge) {\n        return of(cmdMerge);\n    }\n\n    /**\n     * 获取 cmdInfo\n     * <pre>\n     *     内部使用享元工厂来获取 cmdInfo\n     * </pre>\n     *\n     * @param cmd    主路由\n     * @param subCmd 子路由\n     * @return 路由信息\n     */\n    public static CmdInfo of(int cmd, int subCmd) {\n        return of(CmdKit.merge(cmd, subCmd));\n    }\n\n    /**\n     * 获取 cmdInfo\n     * <pre>\n     *     内部使用享元工厂来获取 cmdInfo\n     * </pre>\n     *\n     * @param cmdMerge cmd-subCmd {@link CmdKit#merge(int, int)}\n     * @return 路由信息\n     */\n    public static CmdInfo of(int cmdMerge) {\n        return CmdInfoFlyweightFactory.of(cmdMerge);\n    }\n\n    @Override\n    public String toString() {\n        return CmdKit.mergeToString(this.cmdMerge);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/CmdInfoFlyweightFactory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.common.kit.MoreKit;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 享元工厂\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic final class CmdInfoFlyweightFactory {\n    /**\n     * <pre>\n     * key : cmdMerge\n     * value : cmdInfo\n     * </pre>\n     */\n    static final Map<Integer, CmdInfo> cmdInfoMap = new NonBlockingHashMap<>();\n\n    /**\n     * 获取路由信息\n     * <pre>\n     *     如果不存在，就新建\n     * </pre>\n     *\n     * @param cmd    主路由\n     * @param subCmd 子路由\n     * @return 路由信息\n     */\n    public static CmdInfo of(int cmd, int subCmd) {\n        int cmdMerge = CmdKit.merge(cmd, subCmd);\n        return of(cmdMerge);\n    }\n\n    /**\n     * 获取路由信息\n     * <pre>\n     *     如果不存在，就新建\n     * </pre>\n     *\n     * @param cmdMerge 主路由(高16) + 子路由(低16)\n     * @return 路由信息\n     */\n    public static CmdInfo of(int cmdMerge) {\n\n        CmdInfo cmdInfo = cmdInfoMap.get(cmdMerge);\n\n        // 无锁化\n        if (Objects.isNull(cmdInfo)) {\n            var newValue = new CmdInfo(cmdMerge);\n            return MoreKit.putIfAbsent(cmdInfoMap, cmdMerge, newValue);\n        }\n\n        return cmdInfo;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/CmdKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * Cmd 工具\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@UtilityClass\npublic class CmdKit {\n    /**\n     * 得到主路由\n     * 从 cmdMerge 中获取 [高16位] 的数值\n     *\n     * @param cmdMerge 合并路由 cmdMerge\n     * @return [高16位] 的数值\n     */\n    public int getCmd(int cmdMerge) {\n        return cmdMerge >> 16;\n    }\n\n    /**\n     * 得到子路由\n     * 从 cmdMerge 中获取 [低16位] 的数值\n     *\n     * @param cmdMerge 合并路由 cmdMerge\n     * @return [低16位] 的数值\n     */\n    public int getSubCmd(int cmdMerge) {\n        return cmdMerge & 0xFFFF;\n    }\n\n    /**\n     * 合并两个参数,分别存放在 [高16 和 低16]\n     * <pre>\n     *     cmd - 高16\n     *     subCmd - 低16\n     *     例如 cmd = 1; subCmd = 1;\n     *     mergeCmd 的结果: 65537\n     *     那么 mergeCmd 对应的二进制是: [0000 0000 0000 0001] [0000 0000 0000 0001]\n     * </pre>\n     *\n     * @param cmd    主路由存放于合并结果的高16位, 该参数不得大于 32767\n     * @param subCmd 子路由存放于合并结果的低16位, 该参数不得大于 65535\n     * @return 合并的结果\n     */\n    public int merge(int cmd, int subCmd) {\n        return (cmd << 16) + subCmd;\n    }\n\n    public String mergeToString(int cmdMerge) {\n        int cmd = getCmd(cmdMerge);\n        int subCmd = getSubCmd(cmdMerge);\n        String template = \"[cmd:%d - subCmd:%d - cmdMerge:%d]\";\n        return String.format(template, cmd, subCmd, cmdMerge);\n    }\n\n    public String mergeToShort(int cmdMerge) {\n        int cmd = getCmd(cmdMerge);\n        int subCmd = getSubCmd(cmdMerge);\n        return String.format(\"[cmd:%d-%d %d]\", cmd, subCmd, cmdMerge);\n    }\n\n    public String toString(int cmdMerge) {\n        int cmd = getCmd(cmdMerge);\n        int subCmd = getSubCmd(cmdMerge);\n        String template = \"cmd[%d - %d]\";\n\n        return String.format(template, cmd, subCmd);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/DataCodecKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.codec.DataCodec;\nimport com.iohao.game.action.skeleton.core.codec.ProtoDataCodec;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\n/**\n * 业务框架对业务数据的编解码器\n * <pre>\n *     会在构建业务框架时赋值  DataCodecKit#dataCodec\n *     see {@link BarSkeletonBuilder#build()}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-18\n */\n@UtilityClass\npublic class DataCodecKit {\n\n    /** 业务数据的编解码器 */\n    @Getter\n    @Setter\n    DataCodec dataCodec = new ProtoDataCodec();\n\n    /**\n     * 将业务参数编码成字节数组\n     *\n     * @param data 业务参数 (指的是请求端的请求参数)\n     * @return bytes\n     */\n    public byte[] encode(Object data) {\n        return dataCodec.encode(data);\n    }\n\n    /**\n     * 将字节数组解码成对象\n     *\n     * @param data       业务参数 (指的是请求端的请求参数)\n     * @param paramClazz clazz\n     * @param <T>        t\n     * @return 业务参数\n     */\n    public <T> T decode(byte[] data, Class<T> paramClazz) {\n        return dataCodec.decode(data, paramClazz);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/DefaultActionCommandFlowExecute.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionAfter;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\n\n/**\n * 默认的 action 命令流程执行器\n * <pre>\n *     编排业务框架处理业务类的流程\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-17\n */\nfinal class DefaultActionCommandFlowExecute implements ActionCommandFlowExecute {\n\n    @Override\n    public void execute(final FlowContext flowContext) {\n        // 业务框架\n        BarSkeleton barSkeleton = flowContext.getBarSkeleton();\n        // inout manager\n        InOutManager inOutManager = barSkeleton.inOutManager;\n\n        // 1 ---- fuck前 在调用控制器对应处理方法前, 执行inout的in.\n        inOutManager.fuckIn(flowContext);\n\n        // true 表示没有错误码 。如果在这里有错误码，一般是业务参数验证得到的错误 （即开启了业务框架的验证）\n        boolean notError = !flowContext.getResponse().hasError();\n        if (notError) {\n            // action\n            ActionCommand actionCommand = flowContext.getActionCommand();\n\n            // 2 ---- ActionController 工厂\n            var factoryBean = barSkeleton.getActionFactoryBean();\n            var controller = factoryBean.getBean(actionCommand);\n            // 业务 actionController\n            flowContext.setActionController(controller);\n\n            // 3 ---- fuck中 开始执行控制器方法, 这是真正处理客户端请求的逻辑.\n            var actionMethodInvoke = barSkeleton.getActionMethodInvoke();\n            // 得到业务类的返回结果\n            var result = actionMethodInvoke.invoke(flowContext);\n            flowContext.setMethodResult(result);\n\n            // 4 ---- wrap result 结果包装器\n            var actionMethodResultWrap = barSkeleton.getActionMethodResultWrap();\n            // 结果包装器\n            actionMethodResultWrap.wrap(flowContext);\n        }\n\n        if (flowContext.isExecuteActionAfter()) {\n            // 5 ---- after 一般用于响应数据到 请求端\n            ActionAfter actionAfter = barSkeleton.getActionAfter();\n            actionAfter.execute(flowContext);\n        }\n\n        // 6 ---- fuck后 在调用控制器对应处理方法结束后, 执行inout的out.\n        inOutManager.fuckOut(flowContext);\n    }\n\n    static DefaultActionCommandFlowExecute me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DefaultActionCommandFlowExecute ME = new DefaultActionCommandFlowExecute();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/DefaultActionFactoryBean.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\n/**\n * action 工厂对象 <BR>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\nfinal class DefaultActionFactoryBean<T> implements ActionFactoryBean<T> {\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public T getBean(ActionCommand actionCommand) {\n\n        if (actionCommand.deliveryContainer) {\n            return DependencyInjectionPart.me().getBean(actionCommand);\n        }\n\n        if (actionCommand.isCreateSingleActionCommandController()) {\n            return (T) actionCommand.getActionController();\n        }\n\n        return (T) actionCommand.getActionControllerConstructorAccess().newInstance();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/DependencyInjectionPart.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.lang.annotation.Annotation;\n\n/**\n * 依赖注入的部分\n * <pre>\n *     通常用于集成到第三方框架，如：\n *     spring\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-10-25\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DependencyInjectionPart {\n\n    /** true 与第三方框架集成 */\n    boolean injection;\n    /**\n     * 容器管标签\n     * <pre>\n     *     比如 spring 可以使用 Component 来标记类是交给容器管理的；\n     *\n     *     虽然 spring 还支持其他的注解来标记类可以交给容器管理，\n     *     但 ioGame 只推荐大家使用统一的一个就好了；\n     *\n     *     如果要把 ioGame 集成到其他框架中，也是大概类似的处理方式；\n     * </pre>\n     */\n    Class<? extends Annotation> annotationClass;\n\n    /** 当前使用的 ActionFactoryBean */\n    ActionFactoryBean<?> actionFactoryBean;\n\n    public boolean deliveryContainer(Class<?> controllerClazz) {\n        return controllerClazz.getAnnotation(annotationClass) != null;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getBean(ActionCommand actionCommand) {\n        return (T) actionFactoryBean.getBean(actionCommand);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getBean(Class<?> actionControllerClazz) {\n        return (T) actionFactoryBean.getBean(actionControllerClazz);\n    }\n\n    private DependencyInjectionPart() {\n\n    }\n\n    public static DependencyInjectionPart me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DependencyInjectionPart ME = new DependencyInjectionPart();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/DevConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport lombok.Getter;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\n\n/**\n * 开发时相关的配置类\n *\n * @author 渔民小镇\n * @date 2022-05-19\n */\n@Deprecated\npublic final class DevConfig {\n    /**\n     * true 打印广播日志，默认不打印\n     * <p>\n     * see {@link BarSkeletonBuilderParamConfig#createBuilder()}\n     *\n     * @deprecated 请使用 {@link IoGameCommonCoreConfig#broadcastLog}\n     */\n    @Getter\n    @Deprecated\n    static boolean broadcastLog;\n\n    /**\n     * cmd 路由对应的响应数据类型信息\n     * <pre>\n     *     key : cmdMerge\n     *     value : 路由对应的 class 信息\n     *\n     *     开发阶段的数据辅助信息，目前主要提供给\"模拟客户端\" 时使用的。\n     *\n     *     此 map 中，保存了：\n     *     1 action 的返回值类信息；\n     *     2 广播（推送）时的类信息；\n     *\n     * </pre>\n     */\n    static Map<Integer, Class<?>> cmdDataClassMap = new NonBlockingHashMap<>();\n\n    public static Class<?> getCmdDataClass(int cmdMerge) {\n        return cmdDataClassMap.get(cmdMerge);\n    }\n\n    public static void put(Integer cmdMerge, Class<?> dataClass) {\n        cmdDataClassMap.putIfAbsent(cmdMerge, dataClass);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/Handler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\n\n/**\n * 业务框架处理器\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface Handler {\n    /**\n     * 处理一个action请求\n     *\n     * @param flowContext flowContext\n     * @return 如果返回 false 就不交给下一个链进行处理. 全剧终了.\n     */\n    boolean handler(FlowContext flowContext);\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/InOutManager.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * InOut 插件相关\n *\n * @author 渔民小镇\n * @date 2022-03-08\n */\npublic interface InOutManager {\n    /**\n     * 执行所有 inOut fuckIn 方法\n     *\n     * @param flowContext flowContext\n     */\n    void fuckIn(FlowContext flowContext);\n\n    /**\n     * 执行所有 inOut fuckOut 方法\n     *\n     * @param flowContext flowContext\n     */\n    void fuckOut(FlowContext flowContext);\n\n    /**\n     * 添加 inOut\n     *\n     * @param inOut inOut 插件\n     */\n    void addInOut(ActionMethodInOut inOut);\n\n    /**\n     * 得到插件列表\n     *\n     * @return ActionMethodInOut list\n     */\n    List<ActionMethodInOut> listInOut();\n\n    /**\n     * 通过 clazz 找到对应的 inOut 对象\n     *\n     * @param clazz inOut class\n     * @param <T>   ActionMethodInOut\n     * @return any optional\n     * @see ActionMethodInOut\n     */\n    @SuppressWarnings(\"unchecked\")\n    default <T extends ActionMethodInOut> Optional<T> getOptional(Class<? extends T> clazz) {\n        for (ActionMethodInOut inOut : this.listInOut()) {\n            if (Objects.equals(inOut.getClass(), clazz)) {\n                return (Optional<T>) Optional.of(inOut);\n            }\n        }\n\n        return Optional.empty();\n    }\n\n    /**\n     * 创建 InOutManager 对象实现。inOut 的执行顺序为 in ABC，out ABC\n     *\n     * @return InOutManager ABC, ABC\n     */\n    static InOutManager ofAbcAbc() {\n        return new AbcAbcInOutManager();\n    }\n\n    /**\n     * 创建 InOutManager 对象实现。inOut 的执行顺序为 in ABC，out CBA。（默认策略）\n     *\n     * @return InOutManager ABC, CBA\n     */\n    static InOutManager ofPipeline() {\n        return new PipelineInOutManager();\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/InOutManagerAbout.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class AbcAbcInOutManager implements InOutManager {\n    final List<ActionMethodInOut> inOutList = new ArrayList<>();\n\n    public void fuckIn(FlowContext flowContext) {\n        if (inOutList.isEmpty()) {\n            return;\n        }\n\n        if (this.inOutList.size() == 1) {\n            this.inOutList.getFirst().fuckIn(flowContext);\n            return;\n        }\n\n        for (ActionMethodInOut inOut : this.inOutList) {\n            inOut.fuckIn(flowContext);\n        }\n    }\n\n    public void fuckOut(FlowContext flowContext) {\n        if (inOutList.isEmpty()) {\n            return;\n        }\n\n        if (this.inOutList.size() == 1) {\n            this.inOutList.getFirst().fuckOut(flowContext);\n            return;\n        }\n\n        for (ActionMethodInOut inOut : this.inOutList) {\n            inOut.fuckOut(flowContext);\n        }\n    }\n\n    @Override\n    public void addInOut(ActionMethodInOut inOut) {\n        this.inOutList.add(inOut);\n    }\n\n    public List<ActionMethodInOut> listInOut() {\n        return this.inOutList;\n    }\n}\n\nfinal class PipelineInOutManager implements InOutManager {\n    final List<ActionMethodInOut> inList = new ArrayList<>();\n    final List<ActionMethodInOut> outList = new ArrayList<>();\n\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n        int size = inList.size();\n\n        if (size == 0) {\n            return;\n        }\n\n        if (size == 1) {\n            this.inList.getFirst().fuckIn(flowContext);\n            return;\n        }\n\n        for (ActionMethodInOut inOut : this.inList) {\n            inOut.fuckIn(flowContext);\n        }\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        int size = outList.size();\n\n        if (size == 0) {\n            return;\n        }\n\n        if (size == 1) {\n            this.outList.getFirst().fuckOut(flowContext);\n            return;\n        }\n\n        for (ActionMethodInOut inOut : this.outList) {\n            inOut.fuckOut(flowContext);\n        }\n    }\n\n    @Override\n    public void addInOut(ActionMethodInOut inOut) {\n        this.inList.addLast(inOut);\n        this.outList.addFirst(inOut);\n    }\n\n    @Override\n    public List<ActionMethodInOut> listInOut() {\n        return this.inList;\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/IoGameCommonCoreConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * 业务框架相关的默认配置\n *\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@UtilityClass\npublic class IoGameCommonCoreConfig {\n    public boolean eventBusLog;\n    /**\n     * true 打印广播日志，默认不打印\n     * <p>\n     * see {@link BarSkeletonBuilderParamConfig#createBuilder()}\n     */\n    public boolean broadcastLog;\n\n    public interface ExternalBizCode {\n        /** 用户（玩家）的元信息同步，AttachmentExternalBizRegion */\n        int attachment = -3;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/IoGameGlobalSetting.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.codec.DataCodec;\nimport lombok.experimental.UtilityClass;\n\n/**\n * 业务框架全局配置\n *\n * @author 渔民小镇\n * @date 2022-11-24\n */\n@UtilityClass\npublic class IoGameGlobalSetting {\n    /**\n     * 设置业务数据的编解码器\n     *\n     * @param dataCodec dataCodec\n     */\n    public void setDataCodec(DataCodec dataCodec) {\n        DataCodecKit.setDataCodec(dataCodec);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/PrintActionKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.IoGameVersion;\nimport com.iohao.game.action.skeleton.core.codec.DataCodec;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.runner.Runners;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.ArrayKit;\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.experimental.UtilityClass;\nimport org.fusesource.jansi.Ansi;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * 打印 action\n * <BR>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@UtilityClass\nclass PrintActionKit {\n\n    void print(BarSkeleton barSkeleton, BarSkeletonSetting setting) {\n\n        if (!setting.print) {\n            return;\n        }\n\n        if (setting.isPrintHandler()) {\n            var list = List.of(barSkeleton.getHandlers());\n            PrintActionKit.printHandler(list);\n        }\n\n        if (setting.isPrintInout()) {\n            var list = barSkeleton.inOutManager.listInOut();\n            PrintActionKit.printInout(list);\n        }\n\n        if (setting.isPrintDataCodec()) {\n            PrintActionKit.printDataCodec();\n        }\n\n        if (setting.isPrintRunners()) {\n            extractedRunners(barSkeleton);\n        }\n\n        if (setting.isPrintAction()) {\n            PrintActionKit.printActionCommand(barSkeleton.actionCommandRegions.actionCommands, setting.printActionShort);\n        }\n\n        IoGameBanner.printLine();\n    }\n\n    private static void extractedRunners(BarSkeleton barSkeleton) {\n        Runners runners = barSkeleton.runners;\n        List<String> nameList = runners.listRunnerName();\n        String title = \"@|CYAN ======================== Runners ========================= |@\";\n        IoGameBanner.println1(Ansi.ansi().render(title));\n\n        var printActionKitClose = Bundle.getMessage(MessageKey.printActionKitPrintClose);\n        IoGameBanner.printlnMsg(printActionKitClose + \" BarSkeletonBuilder.setting.printRunners\");\n\n        for (String name : nameList) {\n            String info = String.format(\"@|BLUE %s |@\", name);\n            IoGameBanner.println1(Ansi.ansi().render(info));\n        }\n    }\n\n    /**\n     * 打印 inout\n     *\n     * @param inOuts inOuts\n     */\n    void printInout(List<ActionMethodInOut> inOuts) {\n        var businessFrameworkPlugin = Bundle.getMessage(MessageKey.businessFrameworkPlugin);\n        printTitle(businessFrameworkPlugin);\n\n        var printActionKitClose = Bundle.getMessage(MessageKey.printActionKitPrintClose);\n        IoGameBanner.printlnMsg(printActionKitClose + \" BarSkeletonBuilder.setting.printInout\");\n\n        for (ActionMethodInOut inOut : inOuts) {\n            String info = String.format(\"@|BLUE %s |@\", inOut.getClass());\n            IoGameBanner.println1(Ansi.ansi().render(info));\n        }\n    }\n\n    private void printTitle(String title) {\n        String formatted = \"@|CYAN ======================== %s ========================= |@\".formatted(title);\n        IoGameBanner.println1(Ansi.ansi().render(formatted));\n    }\n\n    void printHandler(List<Handler> handlers) {\n\n        var businessFramework = Bundle.getMessage(MessageKey.businessFramework);\n\n        printTitle(businessFramework);\n        IoGameBanner.printlnMsg(IoGameVersion.VERSION);\n\n        String colorStr = \"@|BLACK BLACK|@ @|RED RED|@ @|GREEN GREEN|@ @|YELLOW YELLOW|@ @|BLUE BLUE|@ @|MAGENTA MAGENTA|@ @|CYAN CYAN|@ @|WHITE WHITE|@ @|DEFAULT DEFAULT|@\";\n        IoGameBanner.println1(Ansi.ansi().render(colorStr));\n\n        printTitle(\"Handler\");\n\n        var printActionKitClose = Bundle.getMessage(MessageKey.printActionKitPrintClose);\n        IoGameBanner.printlnMsg(printActionKitClose + \" BarSkeletonBuilder.setting.printHandler\");\n\n        for (Handler handler : handlers) {\n            String info = String.format(\"@|BLUE %s |@\", handler.getClass());\n            IoGameBanner.println1(Ansi.ansi().render(info));\n        }\n    }\n\n    void printActionCommand(ActionCommand[][] behaviors, boolean shortName) {\n        printTitle(\"action\");\n\n        var printActionKitClose = Bundle.getMessage(MessageKey.printActionKitPrintClose);\n        IoGameBanner.printlnMsg(printActionKitClose + \" BarSkeletonBuilder.setting.printAction\");\n\n        var printActionKitPrintFull = Bundle.getMessage(MessageKey.printActionKitPrintFull);\n        IoGameBanner.printlnMsg(printActionKitPrintFull + \" BarSkeletonBuilder.setting.printActionShort\");\n\n        String cmdName = Bundle.getMessage(MessageKey.cmdName);\n\n        for (int cmd = 0; cmd < behaviors.length; cmd++) {\n            ActionCommand[] subBehaviors = behaviors[cmd];\n\n            if (Objects.isNull(subBehaviors)) {\n                continue;\n            }\n\n            for (int subCmd = 0; subCmd < subBehaviors.length; subCmd++) {\n                ActionCommand subBehavior = subBehaviors[subCmd];\n\n                if (Objects.isNull(subBehavior)) {\n                    continue;\n                }\n\n                ActionCommand.ParamInfo[] paramInfos = subBehavior.getParamInfos();\n                String paramInfo = \"\";\n                String paramInfoShort = \"\";\n\n                if (ArrayKit.notEmpty(paramInfos)) {\n                    paramInfoShort = ArrayKit.join(paramInfos, \", \");\n\n                    paramInfo = Arrays.stream(paramInfos)\n                            .map(theParamInfo -> theParamInfo.toString(true))\n                            .collect(Collectors.joining(\", \"));\n                }\n\n                Map<String, Object> params = new HashMap<>();\n                params.put(\"cmd\", cmd);\n                params.put(\"subCmd\", subCmd);\n                params.put(\"actionName\", subBehavior.getActionControllerClazz().getName());\n                params.put(\"methodName\", subBehavior.getActionMethodName());\n                params.put(\"paramInfo\", paramInfo);\n                params.put(\"paramInfoShort\", paramInfoShort);\n                params.put(\"actionNameShort\", subBehavior.getActionControllerClazz().getSimpleName());\n                params.put(\"throw\", subBehavior.isThrowException() ? \"throws\" : \"\");\n\n                shortName(params, shortName);\n\n                // 返回类型\n                ActionCommand.ActionMethodReturnInfo actionMethodReturnInfo = subBehavior.getActionMethodReturnInfo();\n                params.put(\"returnTypeClazz\", actionMethodReturnInfo.toString());\n                params.put(\"returnTypeClazzShort\", actionMethodReturnInfo.toString(false));\n\n                checkReturnType(actionMethodReturnInfo.getReturnTypeClazz());\n\n                shortName(params, shortName);\n\n                params.put(\"actionSimpleName\", subBehavior.getActionControllerClazz().getSimpleName());\n                params.put(\"lineNumber\", subBehavior.actionCommandDoc.getLineNumber());\n\n                String routeCell = Color.red.format(cmdName + \": {cmd} - {subCmd}\", params);\n                String actionCell = Color.white.wrap(\"--- action :\");\n                String actionNameCell = Color.blue.format(\"{actionName}\", params);\n                String methodNameCell = Color.blue.format(\"{methodName}\", params);\n                String paramInfoCell = Color.green.format(\"{paramInfo}\", params);\n\n                String returnCell = Color.defaults.wrap(\"return\");\n                String returnValueCell = Color.magenta.format(\"{returnTypeClazz}\", params);\n                String throwCell = Color.red.format(\"{throw}\", params);\n\n                params.put(\"routeCell\", routeCell);\n                params.put(\"actionCell\", actionCell);\n                params.put(\"actionNameCell\", actionNameCell);\n                params.put(\"methodNameCell\", methodNameCell);\n                params.put(\"returnCell\", returnCell);\n                params.put(\"returnValueCell\", returnValueCell);\n                params.put(\"throwCell\", throwCell);\n                params.put(\"paramInfoCell\", paramInfoCell);\n\n                String lineTemplate = \"{routeCell} {actionCell} {actionNameCell}.{methodNameCell}({paramInfoCell}) {throwCell} --- return {returnValueCell}  ~~~ see.({actionSimpleName}.java:{lineNumber})\";\n                String text = StrKit.format(lineTemplate, params);\n                IoGameBanner.println1(Ansi.ansi().render(text));\n            }\n        }\n    }\n\n    void printDataCodec() {\n        var printActionKitDataCodec = Bundle.getMessage(MessageKey.printActionKitDataCodec);\n        printTitle(printActionKitDataCodec);\n\n        var printActionKitClose = Bundle.getMessage(MessageKey.printActionKitPrintClose);\n        IoGameBanner.printlnMsg(printActionKitClose + \" BarSkeletonBuilder.setting.printDataCodec\");\n\n        DataCodec dataCodec = DataCodecKit.dataCodec;\n        String info = String.format(\"@|BLUE %s - %s |@\", dataCodec.codecName(), dataCodec.getClass().getName());\n        IoGameBanner.println1(Ansi.ansi().render(info));\n\n    }\n\n    private void shortName(Map<String, Object> params, boolean shortName) {\n        if (!shortName) {\n            return;\n        }\n\n        params.put(\"paramInfo\", params.get(\"paramInfoShort\"));\n        params.put(\"actionName\", params.get(\"actionNameShort\"));\n        params.put(\"returnTypeClazz\", params.get(\"returnTypeClazzShort\"));\n        params.put(\"actualTypeArgumentClazz\", params.get(\"actualTypeArgumentClazzShort\"));\n    }\n\n    private void checkReturnType(final Class<?> returnTypeClazz) {\n        if (Set.class.isAssignableFrom(returnTypeClazz) || Map.class.isAssignableFrom(returnTypeClazz)) {\n            // 参数的不支持不写逻辑了，这里告诉一下就行了。看之后的需要在考虑是否支持吧\n            // Action return values and parameters do not support set, map and basic types!\n            var printActionKitCheckReturnType = Bundle.getMessage(MessageKey.printActionKitCheckReturnType);\n            ThrowKit.ofRuntimeException(printActionKitCheckReturnType);\n        }\n    }\n\n    private static class Color {\n        String start;\n        static final Color red = new Color(\"@|red\");\n        static final Color white = new Color(\"@|white\");\n        static final Color blue = new Color(\"@|blue\");\n        static final Color green = new Color(\"@|green\");\n        static final Color defaults = new Color(\"@|default\");\n        static final Color magenta = new Color(\"@|magenta\");\n\n        public Color(String start) {\n            this.start = start;\n        }\n\n        String wrap(String str) {\n            return start + \" \" + str + \"|@\";\n        }\n\n        String format(String template, Map<String, Object> params) {\n            String str = StrKit.format(template, params);\n            str = wrap(str);\n            return str;\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/SkeletonAttr.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport com.iohao.game.action.skeleton.eventbus.EventBus;\nimport com.iohao.game.action.skeleton.pulse.Pulses;\nimport com.iohao.game.action.skeleton.pulse.core.PulseTransmit;\nimport com.iohao.game.common.kit.attr.AttrOption;\n\n/**\n * 业务框架动态属性\n *\n * @author 渔民小镇\n * @date 2023-04-21\n * @see BarSkeleton\n */\npublic interface SkeletonAttr {\n    /** 脉冲管理器 */\n    AttrOption<Pulses> pulses = AttrOption.valueOf(\"pulses\");\n    /** 当前逻辑服引用 */\n    AttrOption<BrokerClientContext> brokerClientContext = AttrOption.valueOf(\"brokerClientContext\");\n    /** 服务器唯一标识 hash */\n    AttrOption<Integer> logicServerIdHash = AttrOption.valueOf(\"logicServerIdHash\");\n    /** 脉冲生产者的发射器 */\n    AttrOption<PulseTransmit> producerPulseTransmit = AttrOption.valueOf(\"producerPulseTransmit\");\n    /** 脉冲消费者的发射器 */\n    AttrOption<PulseTransmit> consumerPulseTransmit = AttrOption.valueOf(\"consumerPulseTransmit\");\n    /** EventBus 是逻辑服事件总线，与业务框架、逻辑服是 1:1:1 的关系 */\n    AttrOption<EventBus> eventBus = AttrOption.valueOf(\"eventBus\");\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ValidatorKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.common.validation.Validation;\nimport com.iohao.game.common.validation.Validator;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\n\n\n/**\n * 验证相关，主要用户验证业务参数\n * <pre>\n *     符合 JSR-380标准的校验。这里使用 hibernate-validator\n *\n *     用户需引入validation-api的实现，如：hibernate-validator\n *     注意：hibernate-validator还依赖了javax.el，需自行引入。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-16\n */\n@UtilityClass\npublic class ValidatorKit {\n    @Setter\n    private Validator validator;\n\n    public Validator getValidator() throws RuntimeException {\n\n        if (Objects.nonNull(validator)) {\n            return validator;\n        }\n\n        try {\n            validator = Validation.getValidator();\n        } catch (Exception e) {\n            ThrowKit.ofRuntimeException(e);\n        }\n\n        return validator;\n    }\n\n    public String validate(Object data, Class<?>... groups) {\n        // 验证参数\n        return getValidator().validate(data, groups);\n    }\n\n    /**\n     * 参数类型是否需要验证\n     *\n     * @param paramClazz 参数类型\n     * @return true 这是一个需要验证的参数\n     */\n    boolean isValidator(Class<?> paramClazz) {\n        return getValidator().isValidator(paramClazz);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * action 构建时的上下文\n *\n * @author 渔民小镇\n * @date 2024-04-30\n * @since 21.7\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ActionParserContext {\n    /** 业务框架 */\n    BarSkeleton barSkeleton;\n    /** action method */\n    ActionCommand actionCommand;\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.parser;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\n\n/**\n * action 构建时的监听器（钩子）\n *\n * @author 渔民小镇\n * @date 2024-04-30\n * @since 21.7\n */\npublic interface ActionParserListener {\n    /**\n     * subCmd action callback\n     * <pre>\n     *     每个 action 都会调用一次\n     * </pre>\n     *\n     * @param context action 构建时的上下文\n     */\n    void onActionCommand(ActionParserContext context);\n\n    /**\n     * 在 {@link ActionParserListener#onActionCommand(ActionParserContext)} 之后执行\n     *\n     * @param barSkeleton 业务框架\n     */\n    default void onAfter(BarSkeleton barSkeleton) {\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - action 构建时的监听器，开发者可以利用该接口观察 action 构建过程，或者做一些额外的扩展。\n * <p>\n * for example\n * <pre>{@code // 简单打印\n * public final class YourActionParserListener implements ActionParserListener {\n *     @Override\n *     public void onActionCommand(ActionParserContext context) {\n *         ActionCommand actionCommand = context.getActionCommand();\n *         log.info(actionCommand);\n *     }\n * }\n *\n * void test() {\n *     BarSkeletonBuilder builder = ...;\n *     builder.addActionParserListener(new YourActionParserListener());\n * }\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2024-08-05\n * @see com.iohao.game.action.skeleton.core.action.parser.ProtobufActionParserListener\n */\npackage com.iohao.game.action.skeleton.core.action.parser;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/codec/DataCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.codec;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\n\n/**\n * 业务数据的编解码器\n * <pre>\n *     see {@link DataCodecKit}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-18\n */\npublic interface DataCodec {\n    /**\n     * 将数据对象编码成字节数组\n     *\n     * @param data 数据对象\n     * @return bytes\n     */\n    byte[] encode(Object data);\n\n    /**\n     * 将字节数组解码成对象\n     *\n     * @param data      数据对象的字节\n     * @param dataClass 数据对象 class\n     * @param <T>       t\n     * @return 业务参数\n     */\n    <T> T decode(byte[] data, Class<?> dataClass);\n\n    /**\n     * 编解码名\n     *\n     * @return 编解码名\n     */\n    default String codecName() {\n        return this.getClass().getSimpleName();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/codec/DataSelfEncode.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.codec;\n\n/**\n * SelfEncode\n *\n * @author 渔民小镇\n * @date 2024-12-11\n * @since 21.23\n */\npublic interface DataSelfEncode {\n    /**\n     * get encode data\n     *\n     * @return EncodeData\n     * @since 21.23\n     */\n    byte[] encodeData();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/codec/JsonDataCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.codec;\n\n\nimport com.alibaba.fastjson2.JSON;\n\n/**\n * json 使用的 fastjson2\n * <pre>\n *     注意：如果使用该类，需要在你的项目中引入 fastjson2 的依赖\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-11-24\n */\npublic final class JsonDataCodec implements DataCodec {\n    @Override\n    public byte[] encode(Object data) {\n        return JSON.toJSONBytes(data);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> T decode(byte[] data, Class<?> dataClass) {\n        return (T) JSON.parseObject(data, dataClass);\n    }\n\n    @Override\n    public String codecName() {\n        return \"fastjson2\";\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/codec/ProtoDataCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.codec;\n\nimport com.iohao.game.common.consts.CommonConst;\nimport com.iohao.game.common.kit.ProtoKit;\n\nimport java.util.Objects;\n\n/**\n * 业务参数的 proto 编解码器\n *\n * @author 渔民小镇\n * @date 2022-05-18\n */\n@SuppressWarnings(\"unchecked\")\npublic final class ProtoDataCodec implements DataCodec {\n    @Override\n    public byte[] encode(Object data) {\n        return ProtoKit.toBytes(data);\n    }\n\n    @Override\n    public <T> T decode(final byte[] data, Class<?> dataClass) {\n\n        if (Objects.isNull(data)) {\n            return (T) ProtoKit.parseProtoByte(CommonConst.emptyBytes, dataClass);\n        }\n\n        return (T) ProtoKit.parseProtoByte(data, dataClass);\n    }\n\n    @Override\n    public String codecName() {\n        return \"j-protobuf\";\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/codec/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 业务数据的编解码器，<a href=\"https://iohao.github.io/game/docs/manual/data_protocol\">扩展协议</a>。\n * <br/>\n * 简介\n * <pre>\n *     ioGame 支持同样的一套业务代码，且无需做变更任何业务代码就能支持序列化库的切换，如 protobuf、json，或其他的扩展序列化库。\n *     在 ioGame 中切换协议是简单的，只需要一行代码。这种扩展方式基本做到了：零迁移成本、零维护成本、零学习成本。\n * </pre>\n * <p>\n * 下面，我们通过一个示例来简单的说明。\n * <p>\n * 使用 java 类来定义一个业务数据协议\n * <pre>{@code\n * @ProtobufClass\n * @FieldDefaults(level = AccessLevel.PUBLIC)\n * public class Student {\n *     String name;\n * }\n * }</pre>\n * <p>\n * ioGame 中的业务数据协议是通过 java 类来定义的，这么做的优点有\n * <pre>\n *     1. 即使是 java 新手也能看得明白，而通过 DSL 生成的 java 代码，是不可能这么清晰的。\n *     2. 同时，java 类定义的协议既能支持 protobuf ，又能支持 json ，且开发者无需做任何变更，这点是 DSL 做不到的。\n *     3. 还能支持 <a href=\"https://iohao.github.io/game/docs/core/jsr380\">JSR380 验证</a>，在属性上添加 JSR380 相关注解即可，这点是 DSL 做不到的。\n *     4. 此外，还能在协议类中添加一些自定义方法，这点是 DSL 做不到的。\n *     5. 减少学习成本，不需要学习各种 DSL 相关库的语法。\n *\n *     结论，支持 java 代码定义协议的序列化库，会优先做支持。比如，将来支持了 Fury，那么开发者无需变更现有的业务代码，就能直接使用。从而为开发者减少迁移成本、维护成本、学习成本。\n *\n *     相关讨论 <a href=\"https://github.com/iohao/ioGame/issues/317\">issue-317</a>\n * </pre>\n * <p>\n * 协议切换示例\n * <pre>{@code\n * public void chooseCodec() {\n *     // 使用 JSON 编解码\n *     IoGameGlobalSetting.setDataCodec(new JsonDataCodec());\n *     // 使用 Protobuf 编解码\n *     IoGameGlobalSetting.setDataCodec(new ProtoDataCodec());\n *     // 使用 Fury 编解码\n *     IoGameGlobalSetting.setDataCodec(new FuryDataCodec());\n * }\n * }</pre>\n * 不对现有的业务代码做变更，只需设置当前所使用的编解码器就能切换序列化库。\n * 之后，如果支持了 Fury，我们只需要扩展一个 FuryDataCodec 编解码器即可。\n * 这种扩展方式基本做到了：零迁移成本、零维护成本、零学习成本。\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core.codec;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/BroadcastContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\n\nimport java.util.Collection;\n\n/**\n * 广播通讯上下文\n *\n * @author 渔民小镇\n * @date 2022-05-18\n */\npublic interface BroadcastContext {\n    /**\n     * 广播消息给指定用户列表\n     *\n     * @param responseMessage 消息\n     * @param userIdList      指定用户列表 (如果为 null 或 empty 就不会触发)\n     */\n    void broadcast(ResponseMessage responseMessage, Collection<Long> userIdList);\n\n    /**\n     * 广播消息给单个用户\n     *\n     * @param responseMessage 消息\n     * @param userId          userId\n     */\n    void broadcast(ResponseMessage responseMessage, long userId);\n\n    /**\n     * 全服广播\n     *\n     * @param responseMessage 消息\n     */\n    void broadcast(ResponseMessage responseMessage);\n\n    /**\n     * 全服广播\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     */\n    default void broadcast(CmdInfo cmdInfo, Object bizData) {\n        ResponseMessage responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcast(responseMessage);\n    }\n\n    /**\n     * 广播消息给指定用户列表\n     *\n     * @param cmdInfo    广播到此路由\n     * @param bizData    业务数据\n     * @param userIdList 指定用户列表\n     */\n    default void broadcast(CmdInfo cmdInfo, Object bizData, Collection<Long> userIdList) {\n        ResponseMessage responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcast(responseMessage, userIdList);\n    }\n\n    /**\n     * 广播消息给单个用户\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     * @param userId  userId\n     */\n    default void broadcast(CmdInfo cmdInfo, Object bizData, long userId) {\n        ResponseMessage responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcast(responseMessage, userId);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/BroadcastOrderContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\n\nimport java.util.Collection;\n\n/**\n * 广播通讯上下文 有顺序的\n * <pre>\n *     顺序的广播，框架中只使用一个线程来广播数据，确保消息是严格顺序的\n *     如果没有特殊业务需求，建议使用 BroadcastContext\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-07-14\n */\npublic interface BroadcastOrderContext {\n    /**\n     * 广播消息给指定用户列表\n     *\n     * @param responseMessage 消息\n     * @param userIdList      指定用户列表 (如果为 null 或 empty 就不会触发)\n     */\n    void broadcastOrder(ResponseMessage responseMessage, Collection<Long> userIdList);\n\n    /**\n     * 广播消息给单个用户\n     *\n     * @param responseMessage 消息\n     * @param userId          userId\n     */\n    void broadcastOrder(ResponseMessage responseMessage, long userId);\n\n    /**\n     * 全服广播\n     *\n     * @param responseMessage 消息\n     */\n    void broadcastOrder(ResponseMessage responseMessage);\n\n    /**\n     * 全服广播\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     */\n    default void broadcastOrder(CmdInfo cmdInfo, Object bizData) {\n        ResponseMessage responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcastOrder(responseMessage);\n    }\n\n    /**\n     * 广播消息给指定用户列表\n     *\n     * @param cmdInfo    广播到此路由\n     * @param bizData    业务数据\n     * @param userIdList 指定用户列表\n     */\n    default void broadcastOrder(CmdInfo cmdInfo, Object bizData, Collection<Long> userIdList) {\n        ResponseMessage responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcastOrder(responseMessage, userIdList);\n    }\n\n    /**\n     * 广播消息给单个用户\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     * @param userId  userId\n     */\n    default void broadcastOrder(CmdInfo cmdInfo, Object bizData, long userId) {\n        ResponseMessage responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcastOrder(responseMessage, userId);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/BrokerClientContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\n/**\n * 当前服务器上下文\n * <pre>\n *     see BrokerClientHelper\n *\n *     当增加网络通讯聚合概念后，之后的在增加相关的通讯上下文就方便很多了\n *     新增的通讯上下都作为聚合的父类，在使用的分类上也简单了\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface BrokerClientContext extends ChannelContext, SimpleServer {\n    /**\n     * 获取逻辑服 id\n     *\n     * @return id\n     */\n    String getId();\n\n    /**\n     * 发送消息到游戏网关\n     *\n     * @param request 消息\n     * @throws Exception e\n     */\n    void oneway(final Object request) throws Exception;\n\n    /**\n     * Synchronous invocation\n     *\n     * @param request request\n     * @param <T>     t\n     * @return response object\n     * @throws Exception e\n     * @since 21.19\n     */\n    <T> T invokeSync(final Object request) throws Exception;\n\n    /**\n     * 框架网络通讯聚合接口\n     *\n     * @return 框架网络通信聚合接口\n     */\n    CommunicationAggregationContext getCommunicationAggregationContext();\n\n    /**\n     * 推送通讯相关 - 得到广播通讯上下文\n     *\n     * @return 广播通讯上下文\n     */\n    default BroadcastContext getBroadcastContext() {\n        return this.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 推送通讯相关 - 得到顺序的 - 广播通讯上下文\n     *\n     * @return 顺序的 - 广播通讯上下文\n     */\n    default BroadcastOrderContext getBroadcastOrderContext() {\n        return this.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 得到 processor 上下文\n     *\n     * @return processor 上下文\n     */\n    default ProcessorContext getProcessorContext() {\n        return this.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 逻辑服间的相互通信相关 - 得到内部模块通讯上下文\n     *\n     * @return 内部模块通讯上下文\n     */\n    default InvokeModuleContext getInvokeModuleContext() {\n        return this.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 内部模块通讯上下文，内部模块指的是游戏对外服\n     *\n     * @return 游戏对外服通讯上下文\n     */\n    default InvokeExternalModuleContext getInvokeExternalModuleContext() {\n        return this.getCommunicationAggregationContext();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/ChannelContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\n/**\n * 通信通道接口\n * <pre>\n *     用于对 bolt AsyncContext、netty Channel 的包装，这样可以使得业务框架与网络通信框架解耦。\n *     为将来 ioGame 实现微量级架构的使用做准备，也为将来实现一套更符合游戏的网络通信框架做预留准备。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-12-04\n */\npublic interface ChannelContext {\n    /**\n     * 发送响应给请求端\n     *\n     * @param responseObject 响应对象\n     */\n    void sendResponse(Object responseObject);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/CommunicationAggregationContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\n/**\n * 框架网络通讯聚合接口\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\npublic interface CommunicationAggregationContext extends\n        ProcessorContext,\n        BroadcastContext,\n        BroadcastOrderContext,\n        InvokeModuleContext,\n        InvokeExternalModuleContext {\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/InvokeExternalModuleContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalMessage;\n\nimport java.io.Serializable;\n\n/**\n * 内部模块通讯上下文，内部模块指的是游戏对外服\n * <pre>\n *     单个游戏逻辑服与多个游戏对外服通信请求（可跨进程）\n *\n *     为了区别与游戏逻辑服，这里没有在游戏对外服复用业务框架，\n *     而是使用一个业务码来表示路由。\n *     虽然游戏对外服也是逻辑服的一种，如果在游戏对外服中也使用业务框架，\n *     会给游戏开发者造成一种混乱，所以这里使用业务码\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\npublic interface InvokeExternalModuleContext {\n\n    /**\n     * 【游戏逻辑服】访问多个【游戏对外服】\n     * <pre>\n     *     有些数据只存在于游戏对外服，但由于游戏对外服可能会有多个，特别是在分布式场景下。\n     *     所以这里发起请求时，会调用多个游戏对外服来处理这个请求。\n     * </pre>\n     *\n     * @param bizCode 业务码\n     * @param data    业务参数\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    ResponseCollectExternalMessage invokeExternalModuleCollectMessage(int bizCode, Serializable data);\n\n    /**\n     * 【游戏逻辑服】访问多个【游戏对外服】\n     * <pre>\n     *     有些数据只存在于游戏对外服，但由于游戏对外服可能会有多个，特别是在分布式场景下。\n     *     所以这里发起请求时，会调用多个游戏对外服来处理这个请求。\n     * </pre>\n     *\n     * @param bizCode 业务码\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    default ResponseCollectExternalMessage invokeExternalModuleCollectMessage(int bizCode) {\n        return this.invokeExternalModuleCollectMessage(bizCode, null);\n    }\n\n    /**\n     * 【游戏逻辑服】访问多个【游戏对外服】\n     * <pre>\n     *     有些数据只存在于游戏对外服，但由于游戏对外服可能会有多个，特别是在分布式场景下。\n     *     所以这里发起请求时，会调用多个游戏对外服来处理这个请求。\n     * </pre>\n     *\n     * @param request 请求\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    ResponseCollectExternalMessage invokeExternalModuleCollectMessage(RequestCollectExternalMessage request);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/InvokeModuleContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.protocol.collect.ResponseCollectMessage;\n\n/**\n * 内部模块通讯上下文，内部模块指的是游戏逻辑服\n * <pre>\n *     单个逻辑服与单个逻辑服通信请求-有响应值（可跨进程）\n *     单个逻辑服与单个逻辑服通信请求-无响应值（可跨进程）\n *     单个逻辑服与同类型多个逻辑服通信请求（可跨进程）\n * </pre>\n * 获取内部模块通讯上下文\n * <pre>{@code\n *     // 游戏逻辑服通讯上下文\n *     InvokeModuleContext invokeModuleContext = BrokerClientHelper.getInvokeModuleContext();\n * }\n * </pre>\n * <pre>\n *     默认情况下，跨服且有返回值的 action 调用，则都是同步的；\n *     如果想要使用异步的方式，可以通过 CompletableFuture 或虚拟线程来实现。\n * </pre>\n * <p>\n * example async ： 通过 CompletableFuture 实现；\n * <pre>{@code\n *     CompletableFuture<YourMsg> future = CompletableFuture.supplyAsync(() -> {\n *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n *         CmdInfo cmdInfo = ...\n *         // 游戏逻辑服通讯上下文\n *         InvokeModuleContext invokeModuleContext = ...\n *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n *         return invokeModuleContext.invokeModuleMessageData(cmdInfo, YourMsg.class);\n *     });\n *\n *     ... 你的其他逻辑\n *     var msg = future.get();\n *     log.info(\"message : {} \", msg);\n * }\n * </pre>\n * <p>\n * example async ： 通过 CompletableFuture 实现的回调写法；\n * <pre>{@code\n *     CompletableFuture<YourMsg> future = CompletableFuture.supplyAsync(() -> {\n *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n *         CmdInfo cmdInfo = ...\n *         // 游戏逻辑服通讯上下文\n *         InvokeModuleContext invokeModuleContext = ...\n *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n *         return invokeModuleContext.invokeModuleMessageData(cmdInfo, YourMsg.class);\n *     }).thenAccept(msg -> {\n *         // 回调写法\n *         log.info(\"message : {}\", msg);\n *     });\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-06-07\n */\npublic interface InvokeModuleContext {\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的方法，并且不需要返回值\n     * example\n     * <pre>{@code\n     *     // 内部模块通讯上下文，内部模块指的是游戏逻辑服\n     *     InvokeModuleContext invokeModuleContext = ...\n     *     // 请求房间逻辑服来创建房间，并且不需要返回值\n     *     // 路由、业务参数\n     *     invokeModuleContext.invokeModuleVoidMessage(cmdInfo, data);\n     * }\n     * </pre>\n     *\n     * @param cmdInfo cmdInfo\n     * @param data    请求参数\n     */\n    default void invokeModuleVoidMessage(CmdInfo cmdInfo, Object data) {\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(cmdInfo, data);\n        this.invokeModuleVoidMessage(requestMessage);\n    }\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的方法，并且不需要返回值\n     * example\n     * <pre>{@code\n     *     // 内部模块通讯上下文，内部模块指的是游戏逻辑服\n     *     InvokeModuleContext invokeModuleContext = ...\n     *     // 请求房间逻辑服来创建房间，并且不需要返回值\n     *     // 路由、业务参数\n     *     invokeModuleContext.invokeModuleVoidMessage(cmdInfo);\n     * }\n     * </pre>\n     *\n     * @param cmdInfo cmdInfo\n     */\n    default void invokeModuleVoidMessage(CmdInfo cmdInfo) {\n        this.invokeModuleVoidMessage(cmdInfo, null);\n    }\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的方法，并且不需要返回值\n     * example\n     * <pre>{@code\n     *     // 内部模块通讯上下文，内部模块指的是游戏逻辑服\n     *     InvokeModuleContext invokeModuleContext = ...\n     *     // 请求房间逻辑服来创建房间，并且不需要返回值\n     *     // 路由、业务参数\n     *     invokeModuleContext.invokeModuleVoidMessage(requestMessage);\n     * }\n     * </pre>\n     *\n     * @param requestMessage requestMessage\n     */\n    void invokeModuleVoidMessage(RequestMessage requestMessage);\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n     *         CmdInfo cmdInfo = ...\n     *         YourData data = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     *         YourMsg msg = invokeModuleContext.invokeModuleMessageData(cmdInfo, data, YourMsg.class);\n     *         log.info(\"message : {} \", msg);\n     *     }\n     * }\n     * </pre>\n     *\n     * @param cmdInfo 路由信息\n     * @param data    请求参数\n     * @param clazz   response data class\n     * @param <T>     t\n     * @return pb 对象\n     */\n    default <T> T invokeModuleMessageData(CmdInfo cmdInfo, Object data, Class<T> clazz) {\n        ResponseMessage responseMessage = invokeModuleMessage(cmdInfo, data);\n        // 将字节解析成对象\n        byte[] dataContent = responseMessage.getData();\n        return DataCodecKit.decode(dataContent, clazz);\n    }\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n     *         CmdInfo cmdInfo = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     *         YourMsg msg = invokeModuleContext.invokeModuleMessageData(cmdInfo, YourMsg.class);\n     *         log.info(\"message : {} \", msg);\n     *     }\n     * }\n     * </pre>\n     *\n     * @param cmdInfo 路由信息\n     * @param clazz   response data class\n     * @param <T>     t\n     * @return response data 解析后的数据\n     */\n    default <T> T invokeModuleMessageData(CmdInfo cmdInfo, Class<T> clazz) {\n        return this.invokeModuleMessageData(cmdInfo, null, clazz);\n    }\n\n    /**\n     * 根据 RequestMessage 来请求其他子服务器（其他逻辑服）的数据\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         RequestMessage request = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     *         YourMsg msg = invokeModuleContext.invokeModuleMessageData(request, YourMsg.class);\n     *         log.info(\"message : {} \", msg);\n     *     }\n     * }\n     * </pre>\n     *\n     * @param requestMessage RequestMessage\n     * @param clazz          response data class\n     * @param <T>            t\n     * @return response data 解析后的数据\n     */\n    default <T> T invokeModuleMessageData(RequestMessage requestMessage, Class<T> clazz) {\n        ResponseMessage responseMessage = this.invokeModuleMessage(requestMessage);\n\n        // 将字节解析成对象\n        byte[] dataContent = responseMessage.getData();\n        return DataCodecKit.decode(dataContent, clazz);\n    }\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n     *         CmdInfo cmdInfo = ...\n     *         YourData data = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     *         ResponseMessage responseMessage = invokeModuleContext.invokeModuleMessage(cmdInfo, data);\n     *         // 得到逻辑服返回的业务数据\n     *         YourMsg msg = responseMessage.getData(YourMsg.class);\n     *         log.info(\"message : {} \", msg);\n     *     }\n     * }\n     * </pre>\n     *\n     * @param cmdInfo cmdInfo\n     * @param data    请求参数\n     * @return ResponseMessage\n     */\n    default ResponseMessage invokeModuleMessage(CmdInfo cmdInfo, Object data) {\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(cmdInfo, data);\n\n        return this.invokeModuleMessage(requestMessage);\n    }\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n     *         CmdInfo cmdInfo = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     *         ResponseMessage responseMessage = invokeModuleContext.invokeModuleMessage(cmdInfo);\n     *         // 得到逻辑服返回的业务数据\n     *         YourMsg msg = responseMessage.getData(YourMsg.class);\n     *         log.info(\"message : {} \", msg);\n     *     }\n     * }\n     * </pre>\n     *\n     * @param cmdInfo cmdInfo\n     * @return ResponseMessage\n     */\n    default ResponseMessage invokeModuleMessage(CmdInfo cmdInfo) {\n        return this.invokeModuleMessage(cmdInfo, null);\n    }\n\n    /**\n     * 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         RequestMessage request = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他子服务器（其他逻辑服）的数据\n     *         ResponseMessage responseMessage = invokeModuleContext.invokeModuleMessage(request);\n     *         // 得到逻辑服返回的业务数据\n     *         YourMsg msg = responseMessage.getData(YourMsg.class);\n     *         log.info(\"message : {} \", msg);\n     *     }\n     * }\n     * </pre>\n     *\n     * @param requestMessage requestMessage\n     * @return ResponseMessage\n     */\n    ResponseMessage invokeModuleMessage(RequestMessage requestMessage);\n\n    /**\n     * 模块之间的访问，访问【同类型】的多个逻辑服\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n     *         CmdInfo cmdInfo = ...\n     *         YourData data = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他【同类型】的多个子服务器（其他逻辑服）数据\n     *         var responseCollectMessage = invokeModuleContext.invokeModuleCollectMessage(cmdInfo, data);\n     *\n     *         // 每个逻辑服返回的数据集合\n     *         List<ResponseCollectItemMessage> messageList = responseCollectMessage.getMessageList();\n     *\n     *         for (ResponseCollectItemMessage responseCollectItemMessage : messageList) {\n     *             ResponseMessage responseMessage = responseCollectItemMessage.getResponseMessage();\n     *             // 得到逻辑服返回的业务数据\n     *             YourMsg msg = responseMessage.getData(YourMsg.class);\n     *             log.info(\"message : {} \", msg);\n     *         }\n     *     }\n     * }\n     * </pre>\n     *\n     * @param cmdInfo 路由信息\n     * @param data    业务数据\n     * @return ResponseCollectMessage\n     */\n    default ResponseCollectMessage invokeModuleCollectMessage(CmdInfo cmdInfo, Object data) {\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(cmdInfo, data);\n        return this.invokeModuleCollectMessage(requestMessage);\n    }\n\n    /**\n     * 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 路由：这个路由是将要访问逻辑服的路由（表示你将要去的地方）\n     *         CmdInfo cmdInfo = ...\n     *         // 根据路由信息来请求其他【同类型】的多个子服务器（其他逻辑服）数据\n     *         ResponseCollectMessage responseCollectMessage = invokeModuleContext.invokeModuleCollectMessage(cmdInfo);\n     *\n     *         // 每个逻辑服返回的数据集合\n     *         List<ResponseCollectItemMessage> messageList = responseCollectMessage.getMessageList();\n     *\n     *         for (ResponseCollectItemMessage responseCollectItemMessage : messageList) {\n     *             ResponseMessage responseMessage = responseCollectItemMessage.getResponseMessage();\n     *             // 得到逻辑服返回的业务数据\n     *             YourMsg msg = responseMessage.getData(YourMsg.class);\n     *             log.info(\"message : {} \", msg);\n     *        }\n     *    }\n     * }\n     * </pre>\n     *\n     * @param cmdInfo 路由信息\n     * @return ResponseCollectMessage\n     */\n    default ResponseCollectMessage invokeModuleCollectMessage(CmdInfo cmdInfo) {\n        return invokeModuleCollectMessage(cmdInfo, null);\n    }\n\n    /**\n     * 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     * example\n     * <pre>{@code\n     *     public void count() {\n     *         RequestMessage request = ...\n     *         // 模块通讯上下文\n     *         InvokeModuleContext invokeModuleContext = ...\n     *         // 根据路由信息来请求其他【同类型】的多个子服务器（其他逻辑服）数据\n     *         ResponseCollectMessage responseCollectMessage = invokeModuleContext.invokeModuleCollectMessage(request);\n     *\n     *         // 每个逻辑服返回的数据集合\n     *         List<ResponseCollectItemMessage> messageList = responseCollectMessage.getMessageList();\n     *\n     *         for (ResponseCollectItemMessage responseCollectItemMessage : messageList) {\n     *             ResponseMessage responseMessage = responseCollectItemMessage.getResponseMessage();\n     *             // 得到逻辑服返回的业务数据\n     *             YourMsg msg = responseMessage.getData(YourMsg.class);\n     *             log.info(\"message : {} \", msg);\n     *         }\n     *     }\n     * }\n     * </pre>\n     *\n     * @param requestMessage requestMessage\n     * @return ResponseAggregationMessage\n     */\n    ResponseCollectMessage invokeModuleCollectMessage(RequestMessage requestMessage);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/ProcessorContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\n/**\n * 通讯方式之一 用于各服务器之前的 processor 通信\n * <pre>\n *     各服务器指的是\n *          对外服、游戏网关、游戏逻辑服\n *\n *      用于 bolt 扩展 processor，这个种扩展方式是自由度是最大的\n *      但需要开发者自己编写 AsyncUserProcessor 来处理这种消息\n *\n *      实际内部是调用的是 RpcClient#oneway(Url, Object) 方法，所以这个接口是用来区分通讯类型\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-28\n */\npublic interface ProcessorContext {\n    /**\n     * oneway 异步调用\n     *\n     * @param message message\n     */\n    void invokeOneway(Object message);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/SimpleServer.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.commumication;\n\nimport com.iohao.game.action.skeleton.protocol.processor.SimpleServerInfo;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-23\n */\npublic interface SimpleServer {\n    /**\n     * 简单的服务器信息\n     *\n     * @return 简单的服务器信息\n     */\n    SimpleServerInfo getSimpleServerInfo();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/commumication/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 通讯相关接口\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core.commumication;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionCommandDoc.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * action command 文档\n * <pre>\n *     存放源代码信息\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-22\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ActionCommandDoc {\n    int subCmd;\n    ActionCommand actionCommand;\n\n    int classLineNumber = 1;\n    String classComment = \"\";\n\n    /** 代码所在行 */\n    int lineNumber = 1;\n    String comment = \"\";\n\n    /** 方法返回值描述 */\n    String methodReturnComment = \"\";\n    String methodParamComment = \"\";\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionCommandDocKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.ClassScanner;\nimport com.iohao.game.common.kit.io.FileKit;\nimport com.thoughtworks.qdox.JavaProjectBuilder;\nimport com.thoughtworks.qdox.model.JavaClass;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-28\n */\n@UtilityClass\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class ActionCommandDocKit {\n    @Setter\n    Function<URL, String> sourceFilePathFun = resourceUrl -> {\n        String path = resourceUrl.getPath();\n        boolean isMaven = path.contains(\"target/classes\");\n\n        // #459\n        if (!isMaven && path.contains(\".jar!\")) {\n            // jar 包内的路径，目前只处理了 gradle\n            int indexOf = path.indexOf(\":\");\n            if (indexOf != -1) {\n                path = path.substring(indexOf + 1);\n            }\n\n            // 定义正则表达式模式\n            String regex = \"/build/*/.*?\\\\.jar!/\";\n            // 使用正则表达式替换\n            return path.replaceAll(regex, \"/src/main/java/\");\n        }\n\n        return isMaven\n                // maven\n                ? path.replace(\"target/classes\", \"src/main/java\")\n                // gradle\n                : path.replace(\"build/classes\", \"src/main/java\");\n    };\n\n    /**\n     * java class doc map\n     * <pre>\n     *     key : java class name (YourJavaFile.class)\n     *     value : {@link JavaClassDocInfo}\n     * </pre>\n     *\n     * @param controllerList classList\n     * @return map\n     */\n    public Map<String, JavaClassDocInfo> getJavaClassDocInfoMap(List<Class<?>> controllerList) {\n        JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();\n        final Map<String, JavaClassDocInfo> javaClassDocInfoMap = new HashMap<>(controllerList.size());\n\n        for (Class<?> actionClazz : controllerList) {\n            try {\n                String packagePath = actionClazz.getPackageName();\n                ClassScanner classScanner = new ClassScanner(packagePath, null);\n                List<URL> resources = classScanner.listResource();\n\n                for (URL resource : resources) {\n                    String srcPath = sourceFilePathFun.apply(resource);\n\n                    File file = new File(srcPath);\n                    if (!FileKit.exist(file)) {\n                        continue;\n                    }\n\n                    javaProjectBuilder.addSourceTree(file);\n                }\n\n                Collection<JavaClass> classes = javaProjectBuilder.getClasses();\n                for (JavaClass javaClass : classes) {\n                    javaClassDocInfoMap.computeIfAbsent(javaClass.toString(), key -> new JavaClassDocInfo(javaClass));\n                }\n            } catch (IOException e) {\n                log.error(e.getMessage(), e);\n            }\n        }\n\n        return javaClassDocInfoMap;\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionDoc.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Comparator;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n/**\n * action 文档\n *\n * @author 渔民小镇\n * @date 2023-07-13\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ActionDoc {\n    final int cmd;\n    final Class<?> controllerClazz;\n    /**\n     * action method\n     * <pre>\n     *     key: subCmd\n     *     value: ActionCommandDoc\n     * </pre>\n     */\n    final Map<Integer, ActionCommandDoc> actionCommandDocMap = new NonBlockingHashMap<>();\n\n    JavaClassDocInfo javaClassDocInfo;\n\n    public ActionDoc(int cmd, Class<?> controllerClazz) {\n        this.cmd = cmd;\n        this.controllerClazz = controllerClazz;\n    }\n\n    public void addActionCommandDoc(ActionCommandDoc actionCommandDoc) {\n        int subCmd = actionCommandDoc.getSubCmd();\n        this.actionCommandDocMap.put(subCmd, actionCommandDoc);\n    }\n\n    public void addActionCommand(ActionCommand actionCommand) {\n        CmdInfo cmdInfo = actionCommand.getCmdInfo();\n        int subCmd = cmdInfo.getSubCmd();\n        if (actionCommandDocMap.containsKey(subCmd)) {\n            ActionCommandDoc actionCommandDoc = actionCommandDocMap.get(subCmd);\n            actionCommandDoc.setActionCommand(actionCommand);\n        }\n    }\n\n    public Stream<ActionCommandDoc> stream() {\n        return actionCommandDocMap\n                .values()\n                .stream()\n                .sorted(Comparator.comparingInt(ActionCommandDoc::getSubCmd));\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof ActionDoc that)) {\n            return false;\n        }\n\n        return cmd == that.cmd && Objects.equals(controllerClazz, that.controllerClazz);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(cmd, controllerClazz);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionDocs.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\n/**\n * action 文档管理\n *\n * @author 渔民小镇\n * @date 2023-07-13\n * @deprecated 请使用 {@link IoGameDocumentHelper}\n */\n@Deprecated\npublic final class ActionDocs {\n    /**\n     * 获取 ActionDoc，如果 ActionDoc 不存在则创建\n     *\n     * @param cmd             主路由\n     * @param controllerClazz action class\n     * @return 一定不为 null\n     */\n    public static ActionDoc ofActionDoc(int cmd, Class<?> controllerClazz) {\n        return IoGameDocumentHelper.ofActionDoc(cmd, controllerClazz);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionDocument.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Action Document\n *\n * @author 渔民小镇\n * @date 2024-06-26\n * @since 21.11\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ActionDocument {\n    final ActionDoc actionDoc;\n    final TypeMappingDocument typeMappingDocument;\n\n    List<ActionMemberCmdDocument> actionMemberCmdDocumentList = new ArrayList<>();\n    List<ActionMethodDocument> actionMethodDocumentList = new ArrayList<>();\n\n    ActionDocument(ActionDoc actionDoc, TypeMappingDocument typeMappingDocument) {\n        this.actionDoc = actionDoc;\n        this.typeMappingDocument = typeMappingDocument;\n    }\n\n    void analyse() {\n        // action 方法\n        actionDoc.stream().forEach(actionCommandDoc -> {\n            var cmdInfo = actionCommandDoc.getActionCommand().getCmdInfo();\n            var authentication = IoGameDocumentHelper.getDocumentAccessAuthentication();\n            var cmdMerge = cmdInfo.getCmdMerge();\n            // 路由访问权限控制\n            if (authentication.reject(cmdMerge)) {\n                return;\n            }\n\n            // 成员变量\n            actionMemberCmdDocumentList.add(generateMemberCmdCode(actionCommandDoc));\n            // 方法\n            var actionMethodDocument = new ActionMethodDocument(actionCommandDoc, typeMappingDocument);\n            actionMethodDocumentList.add(actionMethodDocument);\n        });\n    }\n\n    private ActionMemberCmdDocument generateMemberCmdCode(ActionCommandDoc actionCommandDoc) {\n        ActionCommand actionCommand = actionCommandDoc.getActionCommand();\n\n        CmdInfo cmdInfo = actionCommand.getCmdInfo();\n        int cmd = cmdInfo.getCmd();\n        int subCmd = cmdInfo.getSubCmd();\n\n        String comment = actionCommandDoc.getComment();\n        String actionMethodName = actionCommand.getActionMethodName();\n        String memberName = \"%s_%d_%d\".formatted(actionMethodName, cmd, subCmd);\n\n        return new ActionMemberCmdDocument(cmd, subCmd, memberName, comment);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionMemberCmdDocument.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport lombok.Getter;\nimport lombok.Setter;\n\n/**\n * action 成员变量的路由文档\n *\n * @author 渔民小镇\n * @date 2024-06-26\n * @since 21.11\n */\n@Getter\n@Setter\npublic final class ActionMemberCmdDocument {\n    final int cmd;\n    final int subCmd;\n    final int cmdMerge;\n    String comment;\n    String memberName;\n\n    ActionMemberCmdDocument(int cmd, int subCmd, String memberName, String comment) {\n        this.cmd = cmd;\n        this.subCmd = subCmd;\n        this.cmdMerge = CmdKit.merge(cmd, subCmd);\n        this.comment = comment;\n        this.memberName = memberName;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionMethodDocument.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2024-06-26\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic final class ActionMethodDocument {\n    final ActionCommandDoc actionCommandDoc;\n    final TypeMappingDocument typeMappingDocument;\n    /** 类名 */\n    final String actionSimpleName;\n    /** 方法名 */\n    String actionMethodName;\n    /** 方法的注释 */\n    final String methodComment;\n\n    /** true 表示方法有参数 */\n    final boolean hasBizData;\n    /** 方法参数的名字 */\n    String bizDataName;\n    /** 方法参数的类型 class */\n    Class<?> bizDataTypeClazz;\n    /** 方法参数的类型 */\n    String bizDataType;\n    /** 方法参数的注释 */\n    String bizDataComment;\n    /** true 表示参数是 List 类型 */\n    boolean bizDataTypeIsList;\n    /** true 表示协议碎片，false 表示开发者自定义的协议 */\n    boolean internalBizDataType;\n    /** 参数类型（原始的，即使参数是 List，也会取泛型） */\n    String actualTypeName;\n\n    /** 使用的路由成员变量名 */\n    String memberCmdName;\n    /** 使用的 SDK api 名 */\n    String sdkMethodName;\n\n    boolean isVoid;\n    /** 方法返回值的注释 */\n    String returnComment;\n    String returnDataName;\n    /** 返回值类型 class */\n    Class<?> returnTypeClazz;\n    /** 返回值类型（原始的，即使参数是 List，也会取泛型） */\n    String returnDataActualTypeName;\n\n    boolean returnDataIsList;\n    /** true 表示协议碎片，false 表示开发者自定义的协议 */\n    boolean returnDataTypeIsInternal;\n    /** sdk result get 方法名 */\n    String resultMethodTypeName;\n    /** sdk result get list 方法名 */\n    String resultMethodListTypeName;\n\n    public ActionMethodDocument(ActionCommandDoc actionCommandDoc, TypeMappingDocument typeMappingDocument) {\n        this.actionCommandDoc = actionCommandDoc;\n        this.typeMappingDocument = typeMappingDocument;\n\n        ActionCommand actionCommand = actionCommandDoc.getActionCommand();\n        this.actionSimpleName = actionCommand.getActionControllerClazz().getSimpleName();\n\n        // 方法名\n        var documentMethod = actionCommand.getAnnotation(DocumentMethod.class);\n        if (Objects.nonNull(documentMethod)) {\n            this.actionMethodName = StrKit.firstCharToUpperCase(documentMethod.value());\n        } else {\n            this.actionMethodName = StrKit.firstCharToUpperCase(actionCommand.getActionMethodName());\n        }\n\n        // 方法注释\n        this.methodComment = this.actionCommandDoc.getComment();\n\n        CmdInfo cmdInfo = actionCommand.getCmdInfo();\n        this.memberCmdName = \"%s_%d_%d\".formatted(actionCommand.getActionMethodName(), cmdInfo.getCmd(), cmdInfo.getSubCmd());\n\n        // --------- 方法返回值相关 ---------\n        extractedReturnInfo(actionCommand);\n\n        // --------- 方法参数相关 ---------\n        ActionCommand.ParamInfo bizParam = DocumentAnalyseKit.getBizParam(actionCommand);\n        this.hasBizData = Objects.nonNull(bizParam);\n        if (this.hasBizData) {\n            extractedParamInfo(bizParam, actionCommandDoc);\n        }\n    }\n\n    private void extractedReturnInfo(ActionCommand actionCommand) {\n        // 方法返回值注释\n        this.returnComment = actionCommandDoc.getMethodReturnComment();\n\n        ActionCommand.ActionMethodReturnInfo returnInfo = actionCommand.getActionMethodReturnInfo();\n        this.isVoid = returnInfo.isVoid();\n        this.returnDataIsList = returnInfo.isList();\n\n        this.returnTypeClazz = returnInfo.getActualTypeArgumentClazz();\n        var typeMappingRecord = typeMappingDocument.getTypeMappingRecord(returnTypeClazz);\n        this.returnDataName = typeMappingRecord.getParamTypeName();\n        this.returnDataTypeIsInternal = typeMappingRecord.isInternalType();\n        this.resultMethodTypeName = typeMappingRecord.getResultMethodTypeName();\n        this.resultMethodListTypeName = typeMappingRecord.getResultMethodListTypeName();\n\n        this.returnDataActualTypeName = typeMappingRecord.getParamTypeName();\n    }\n\n    private void extractedParamInfo(ActionCommand.ParamInfo paramInfo, ActionCommandDoc actionCommandDoc) {\n        this.bizDataTypeClazz = paramInfo.getActualTypeArgumentClazz();\n        // 方法参数类型\n        var typeMappingRecord = this.typeMappingDocument.getTypeMappingRecord(bizDataTypeClazz);\n        this.bizDataTypeIsList = paramInfo.isList();\n        this.internalBizDataType = typeMappingRecord.isInternalType();\n\n        // sdk 方法名\n        this.sdkMethodName = typeMappingRecord.getOfMethodTypeName(this.bizDataTypeIsList);\n\n        this.bizDataType = typeMappingRecord.getParamTypeName(this.bizDataTypeIsList);\n        this.bizDataName = paramInfo.getName();\n        this.bizDataComment = actionCommandDoc.getMethodParamComment();\n\n        this.actualTypeName = typeMappingRecord.getParamTypeName();\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionSendDoc.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.annotation.DocActionSend;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport lombok.AccessLevel;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2022-02-01\n * @deprecated 请使用 {@link BroadcastDocument}\n */\n@Data\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Deprecated\npublic final class ActionSendDoc {\n    /** 主路由 */\n    final int cmd;\n    /** 子路由 */\n    final int subCmd;\n    /** 业务类型 */\n    Class<?> dataClass;\n    /** 推送描述 */\n    String description;\n    /** 业务参数 */\n    String dataClassName;\n    /** 广播业务参数的描述 */\n    String dataDescription;\n    /** 广播业务参数是否是 List */\n    boolean list;\n    String methodName;\n\n    public ActionSendDoc(DocActionSend docActionSend) {\n        this(docActionSend.cmd(), docActionSend.subCmd(), docActionSend.dataClass(), docActionSend.description());\n    }\n\n    public ActionSendDoc(CmdInfo cmdInfo, Class<?> dataClass, String description) {\n        this(cmdInfo.getCmd(), cmdInfo.getSubCmd(), dataClass, description);\n    }\n\n    public ActionSendDoc(int cmd, int subCmd, Class<?> dataClass, String description) {\n        this.cmd = cmd;\n        this.subCmd = subCmd;\n        this.dataClass = dataClass;\n        this.description = description;\n    }\n\n    public ActionSendDoc(CmdInfo cmdInfo) {\n        this.cmd = cmdInfo.getCmd();\n        this.subCmd = cmdInfo.getSubCmd();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionSendDocs.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.annotation.DocActionSend;\nimport com.iohao.game.action.skeleton.annotation.DocActionSends;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport lombok.Getter;\nimport org.jctools.maps.NonBlockingHashMap;\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilder;\n\nimport java.util.*;\nimport java.util.function.Predicate;\n\n/**\n * @author 渔民小镇\n * @date 2022-02-01\n * @deprecated 请使用 {@link BarSkeletonBuilder#addBroadcastDoc(BroadcastDocBuilder)}\n */\n@Getter\n@Deprecated\npublic final class ActionSendDocs {\n\n    Map<Integer, ActionSendDoc> actionSendDocMap = new NonBlockingHashMap<>();\n\n    public void add(ActionSendDoc actionSendDoc) {\n        this.put(actionSendDoc);\n    }\n\n    private void put(ActionSendDoc actionSendDoc) {\n        int cmdMerge = CmdKit.merge(actionSendDoc.getCmd(), actionSendDoc.getSubCmd());\n        actionSendDocMap.put(cmdMerge, actionSendDoc);\n    }\n\n    public void buildActionSendDoc(List<Class<?>> actionSendClassList) {\n\n        Set<Class<?>> classSet = new HashSet<>(actionSendClassList);\n\n        // 条件: 类上配置了 ActionController 注解\n        Predicate<Class<?>> predicate = controllerClazz -> Objects.nonNull(controllerClazz.getAnnotation(DocActionSends.class));\n\n        classSet.stream().filter(predicate).forEach(actionSendClass -> {\n            DocActionSends annotation = actionSendClass.getAnnotation(DocActionSends.class);\n            DocActionSend[] docActionSends = annotation.value();\n            for (DocActionSend docActionSend : docActionSends) {\n                ActionSendDoc actionSendDoc = new ActionSendDoc(docActionSend);\n                this.put(actionSendDoc);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ActionSendDocsRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport lombok.Getter;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2022-02-01\n * @deprecated 请使用 {@link IoGameDocument}\n */\n@Getter\n@Deprecated\npublic final class ActionSendDocsRegion {\n    Map<Integer, ActionSendDoc> actionSendDocMap = new NonBlockingHashMap<>();\n\n    public void addActionSendDocs(ActionSendDocs actionSendDocs) {\n        this.actionSendDocMap.putAll(actionSendDocs.getActionSendDocMap());\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/BarSkeletonDoc.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport lombok.Setter;\n\nimport java.util.*;\n\n/**\n * 游戏文档生成\n *\n * @author 渔民小镇\n * @date 2022-01-23\n * @deprecated 请使用 {@link IoGameDocumentHelper}\n */\n@Deprecated\npublic final class BarSkeletonDoc {\n\n    final List<BarSkeleton> skeletonList = new LinkedList<>();\n\n    @Setter\n    String docFileName = \"doc_game.txt\";\n\n    @Setter\n    String docPath;\n\n    /**\n     * 只有当 this.generateDoc 为 true 时，才会执行 set 操作\n     *\n     * @param generateDoc generateDoc\n     */\n    public void setGenerateDoc(boolean generateDoc) {\n        IoGameDocumentHelper.setGenerateDoc(generateDoc);\n    }\n\n    public void addSkeleton(BarSkeleton barSkeleton) {\n        skeletonList.add(barSkeleton);\n    }\n\n    public void buildDoc() {\n        IoGameDocumentHelper.generateDocument();\n    }\n\n    @Deprecated\n    public void buildDoc(String docPath) {\n        buildDoc();\n    }\n\n    private BarSkeletonDoc() {\n    }\n\n    public static BarSkeletonDoc me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final BarSkeletonDoc ME = new BarSkeletonDoc();\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/BroadcastDoc.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\n\n/**\n * @author 渔民小镇\n * @date 2024-05-18\n * @since 21.8\n * @deprecated 请使用 {@link BroadcastDocument}\n */\n@Deprecated\npublic interface BroadcastDoc {\n    /**\n     * newBuilder\n     *\n     * @param cmdInfo cmdInfo\n     * @return BroadcastDocBuilder\n     * @deprecated 请使用 {@link BroadcastDocument#newBuilder(CmdInfo)}\n     */\n    static BroadcastDocBuilder newBuilder(CmdInfo cmdInfo) {\n        return new BroadcastDocBuilder(cmdInfo);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/BroadcastDocBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport lombok.AccessLevel;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 广播文档构建器\n *\n * @author 渔民小镇\n * @date 2024-05-18\n * @since 21.8\n * @deprecated 请使用 {@link BroadcastDocumentBuilder}\n */\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PACKAGE)\n@Deprecated\npublic final class BroadcastDocBuilder {\n    final BroadcastDocumentBuilder proxy;\n\n    BroadcastDocBuilder(CmdInfo cmdInfo) {\n        this.proxy = BroadcastDocument.newBuilder(cmdInfo);\n    }\n\n    public BroadcastDocBuilder setMethodDescription(String methodDescription) {\n        this.proxy.setMethodDescription(methodDescription);\n        return this;\n    }\n\n\n    public BroadcastDocBuilder setDataDescription(String dataDescription) {\n        this.proxy.setDataDescription(dataDescription);\n        return this;\n    }\n\n    public BroadcastDocBuilder setList(boolean list) {\n        this.proxy.setList(list);\n        return this;\n    }\n\n    public BroadcastDocBuilder setMethodName(String methodName) {\n        this.proxy.setMethodName(methodName);\n        return this;\n    }\n\n    /**\n     * set 广播（推送）描述\n     *\n     * @param description 广播（推送）描述\n     * @return this\n     */\n    public BroadcastDocBuilder setDescription(String description) {\n        this.proxy.setMethodDescription(description);\n        return this;\n    }\n\n    /**\n     * set 推送的数据类型 List dataClass，ByteValueList dataClass。\n     *\n     * @param dataClass 数据类型\n     * @return this\n     */\n    public BroadcastDocBuilder setDataClassList(Class<?> dataClass) {\n        return this.setDataClassList(dataClass, \"\");\n    }\n\n    public BroadcastDocBuilder setDataClassList(Class<?> dataClass, String dataDescription) {\n        this.proxy.setDataClassList(dataClass, dataDescription);\n        return this;\n    }\n\n    /**\n     * set 推送的数据类型\n     *\n     * @param dataClass 推送的数据类型\n     * @return this\n     */\n    public BroadcastDocBuilder setDataClass(Class<?> dataClass) {\n        return setDataClass(dataClass, null);\n    }\n\n    /**\n     * set 推送的数据类型\n     *\n     * @param dataClass       推送的数据类型\n     * @param dataDescription 业务数据描述\n     * @return this\n     */\n    public BroadcastDocBuilder setDataClass(Class<?> dataClass, String dataDescription) {\n\n        this.proxy.setDataClass(dataClass, dataDescription);\n\n        return this;\n    }\n\n    /**\n     * 构建广播文档\n     *\n     * @return 广播文档\n     * @deprecated 请使用 {@link BroadcastDocBuilder#buildDocument()}\n     */\n    @Deprecated\n    public ActionSendDoc build() {\n        return null;\n    }\n\n    /**\n     * 构建广播文档\n     *\n     * @return BroadcastDocument 广播文档\n     */\n    public BroadcastDocument buildDocument() {\n        return this.proxy.build();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/BroadcastDocument.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 广播文档\n *\n * @author 渔民小镇\n * @date 2024-06-25\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic final class BroadcastDocument {\n    /** 路由 */\n    final CmdInfo cmdInfo;\n    /** 推送方法描述 */\n    String methodDescription;\n    /** 方法名 */\n    String methodName;\n    String cmdMethodName;\n\n    /** 业务类型 */\n    Class<?> dataClass;\n    /** 业务参数 */\n    String dataClassName;\n    /** 广播业务参数的描述 */\n    String dataDescription;\n\n    /** true 表示协议碎片，false 表示开发者自定义的协议 */\n    boolean dataTypeIsInternal;\n    /** 广播业务参数是否是 List */\n    boolean dataIsList;\n\n    String bizDataType;\n\n    /** sdk result get 方法名 */\n    String resultMethodTypeName;\n    /** sdk result get list 方法名 */\n    String resultMethodListTypeName;\n\n    String dataActualTypeName;\n\n    String exampleCode;\n    String exampleCodeAction;\n\n    public int getCmdMerge() {\n        return this.cmdInfo.getCmdMerge();\n    }\n\n    public int getCmd() {\n        return this.cmdInfo.getCmd();\n    }\n\n    public int getSubCmd() {\n        return this.cmdInfo.getSubCmd();\n    }\n\n    BroadcastDocument(CmdInfo cmdInfo) {\n        this.cmdInfo = cmdInfo;\n    }\n\n    public static BroadcastDocumentBuilder newBuilder(CmdInfo cmdInfo) {\n        return new BroadcastDocumentBuilder(cmdInfo);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/BroadcastDocumentBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.core.codec.ProtoDataCodec;\nimport com.iohao.game.action.skeleton.protocol.wrapper.ByteValueList;\nimport com.iohao.game.action.skeleton.protocol.wrapper.WrapperKit;\nimport com.iohao.game.common.kit.ProtoKit;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * 广播文档构建器\n *\n * @author 渔民小镇\n * @date 2024-07-05\n * @since 21.11\n */\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic class BroadcastDocumentBuilder {\n    /** 路由 */\n    final CmdInfo cmdInfo;\n\n    /** 业务数据类型 */\n    Class<?> dataClass;\n    @Setter(AccessLevel.PRIVATE)\n    String dataClassName;\n    /** 广播业务参数的描述 */\n    String dataDescription;\n\n    @Setter(AccessLevel.PACKAGE)\n    boolean list;\n\n    /** 广播方法名，仅在生成客户端代码时使用 */\n    String methodName;\n    /** 广播（推送）描述 */\n    String methodDescription;\n\n    BroadcastDocumentBuilder(CmdInfo cmdInfo) {\n        this.cmdInfo = cmdInfo;\n    }\n\n    /**\n     * set 推送的数据类型 List dataClass，ByteValueList dataClass。\n     *\n     * @param dataClass 数据类型\n     * @return this\n     */\n    public BroadcastDocumentBuilder setDataClassList(Class<?> dataClass) {\n        return this.setDataClassList(dataClass, null);\n    }\n\n    public BroadcastDocumentBuilder setDataClassList(Class<?> dataClass, String dataDescription) {\n        this.list = true;\n        this.dataClass = dataClass;\n        this.dataDescription = dataDescription;\n\n        WrapperKit.optionalValueRecord(dataClass).ifPresentOrElse(valueRecord -> {\n            this.dataClassName = valueRecord.getValueListClazz().getSimpleName();\n\n            if (StrKit.isEmpty(dataDescription)) {\n                this.dataDescription = this.dataClassName;\n            }\n        }, () -> {\n            String simpleName = ByteValueList.class.getSimpleName();\n            String simpleNameActualClazz = dataClass.getSimpleName();\n            this.dataClassName = String.format(\"%s<%s>\", simpleName, simpleNameActualClazz);\n        });\n\n        return this;\n    }\n\n    /**\n     * set 推送的数据类型\n     *\n     * @param dataClass 推送的数据类型\n     * @return this\n     */\n    public BroadcastDocumentBuilder setDataClass(Class<?> dataClass) {\n        return setDataClass(dataClass, null);\n    }\n\n    /**\n     * set 推送的数据类型\n     *\n     * @param dataClass       推送的数据类型\n     * @param dataDescription 业务数据描述\n     * @return this\n     */\n    public BroadcastDocumentBuilder setDataClass(Class<?> dataClass, String dataDescription) {\n\n        this.dataClass = dataClass;\n        this.dataDescription = dataDescription;\n\n        WrapperKit.optionalValueRecord(dataClass).ifPresentOrElse(valueRecord -> {\n            this.dataClassName = valueRecord.getValueClazz().getSimpleName();\n\n            if (StrKit.isEmpty(dataDescription)) {\n                this.dataDescription = this.dataClassName;\n            }\n        }, () -> this.dataClassName = dataClass.getSimpleName());\n\n        return this;\n    }\n\n    private String getMethodName() {\n        // 如果没有指定广播的方法名，则方法名使用下述规则\n        return Objects.isNull(methodName)\n                ? \"Method_%d_%d\".formatted(cmdInfo.getCmd(), cmdInfo.getSubCmd())\n                : methodName;\n    }\n\n    /**\n     * 构建广播文档对象\n     *\n     * @return BroadcastDocument 广播文档\n     */\n    public BroadcastDocument build() {\n        String theMethodName = getMethodName();\n\n        extractedPreparedProto();\n\n        return new BroadcastDocument(this.cmdInfo)\n                // 方法相关\n                .setMethodDescription(this.methodDescription)\n                .setMethodName(theMethodName)\n                .setCmdMethodName(StrKit.firstCharToLowerCase(theMethodName))\n                // 业务参数相关\n                .setDataClass(this.dataClass)\n                .setDataClassName(this.dataClassName)\n                .setDataDescription(this.dataDescription)\n                .setDataIsList(this.list);\n    }\n\n    private void extractedPreparedProto() {\n        if (DataCodecKit.getDataCodec() instanceof ProtoDataCodec) {\n            Optional.ofNullable(this.dataClass)\n                    .filter(clazz -> Objects.nonNull(clazz.getAnnotation(ProtobufClass.class)))\n                    .ifPresent(ProtoKit::create);\n        }\n    }\n\n    /**\n     * 构建广播文档对象，并添加到 {@link IoGameDocumentHelper}\n     */\n    public void buildToDocument() {\n        IoGameDocumentHelper.addBroadcastDocument(this.build());\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.action.skeleton.core.flow.parser.MethodParsers;\nimport com.iohao.game.action.skeleton.protocol.wrapper.ByteValueList;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.Getter;\n\nimport java.util.*;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-29\n */\n@Getter\nfinal class DocInfo {\n    String actionSimpleName;\n    String classComment;\n    Map<Integer, BroadcastDocument> broadcastDocumentMap;\n\n    final List<Map<String, String>> subBehaviorList = new ArrayList<>();\n\n    void setHead(ActionCommand subBehavior) {\n        ActionCommandDoc actionCommandDoc = subBehavior.getActionCommandDoc();\n        this.actionSimpleName = subBehavior.getActionControllerClazz().getSimpleName();\n        this.classComment = actionCommandDoc.getClassComment();\n    }\n\n    void add(ActionCommand subBehavior) {\n        Map<String, String> paramMap = new HashMap<>(16);\n        subBehaviorList.add(paramMap);\n\n        ActionCommandDoc actionCommandDoc = subBehavior.getActionCommandDoc();\n\n        int cmd = subBehavior.getCmdInfo().getCmd();\n        int subCmd = subBehavior.getCmdInfo().getSubCmd();\n        var actionMethodReturnInfo = subBehavior.getActionMethodReturnInfo();\n\n        paramMap.put(\"cmd\", String.valueOf(cmd));\n        paramMap.put(\"subCmd\", String.valueOf(subCmd));\n        paramMap.put(\"actionSimpleName\", subBehavior.getActionControllerClazz().getSimpleName());\n        paramMap.put(\"methodName\", subBehavior.getActionMethodName());\n        paramMap.put(\"methodComment\", actionCommandDoc.getComment());\n        paramMap.put(\"methodParam\", \"\");\n        paramMap.put(\"returnTypeClazz\", returnToString(actionMethodReturnInfo));\n        paramMap.put(\"lineNumber\", String.valueOf(actionCommandDoc.getLineNumber()));\n\n        // 方法参数\n        Arrays.stream(subBehavior.getParamInfos())\n                .filter(paramInfo -> !paramInfo.isFlowContext())\n                .map(this::paramInfoToString)\n                .forEach(methodParam -> paramMap.put(\"methodParam\", methodParam));\n\n        if (subBehavior.isThrowException()) {\n            paramMap.put(\"error\", \"\");\n        }\n\n        paramMap.put(\"methodParamComment\", actionCommandDoc.getMethodParamComment());\n        paramMap.put(\"methodReturnComment\", actionCommandDoc.getMethodReturnComment());\n    }\n\n    private String paramInfoToString(ActionCommand.ParamInfo paramInfo) {\n        Class<?> actualClazz = paramInfo.getActualClazz();\n        boolean isCustomList = paramInfo.isList() && !MethodParsers.containsKey(actualClazz);\n        return paramResultInfoToString(actualClazz, isCustomList);\n    }\n\n    private String returnToString(ActionCommand.ActionMethodReturnInfo actionMethodReturnInfo) {\n        Class<?> actualClazz = actionMethodReturnInfo.getActualClazz();\n        boolean isCustomList = actionMethodReturnInfo.isList() && !MethodParsers.containsKey(actualClazz);\n        return paramResultInfoToString(actualClazz, isCustomList);\n    }\n\n    private String paramResultInfoToString(Class<?> actualClazz, boolean isCustomList) {\n        if (isCustomList) {\n            /*\n             * 因为是生成对接文档，所以不能使用 List<xxx> 来表示，而是使用 ByteValueList<xxx> 来表示。\n             * 因为 ByteValueList 是一个类似 IntValueList、LongValueList 这样的包装类\n             */\n            String simpleName = ByteValueList.class.getSimpleName();\n            String simpleNameActualClazz = actualClazz.getSimpleName();\n            return String.format(\"%s<%s>\", simpleName, simpleNameActualClazz);\n        }\n\n        return actualClazz.getSimpleName();\n    }\n\n    String render() {\n        if (this.subBehaviorList.isEmpty()) {\n            return \"\";\n        }\n\n        String separator = System.lineSeparator();\n\n        List<String> lineList = new ArrayList<>();\n\n        String templateHead = \"==================== {} {} ====================\";\n        lineList.add(StrKit.format(templateHead, this.actionSimpleName, this.classComment));\n\n        String subActionCommandTemplate =\n                \"路由: {cmd} - {subCmd}  --- 【{methodComment}】 --- 【{actionSimpleName}:{lineNumber}】【{methodName}】\";\n\n        for (Map<String, String> paramMap : subBehaviorList) {\n\n            String format = StrKit.format(subActionCommandTemplate, paramMap);\n            lineList.add(format);\n\n            if (paramMap.containsKey(\"error\")) {\n                lineList.add(\"    触发异常: (方法有可能会触发异常)\");\n            }\n\n            // 方法参数\n            if (StrKit.isNotEmpty(paramMap.get(\"methodParam\"))) {\n                format = StrKit.format(\"    方法参数: {methodParam} {methodParamComment}\", paramMap);\n                lineList.add(format);\n            }\n\n            // 方法返回值\n            if (StrKit.isNotEmpty(paramMap.get(\"returnTypeClazz\"))) {\n                format = StrKit.format(\"    方法返回值: {returnTypeClazz} {methodReturnComment}\", paramMap);\n                lineList.add(format);\n            }\n\n            // 广播推送\n            int cmd = Integer.parseInt(paramMap.get(\"cmd\"));\n            int subCmd = Integer.parseInt(paramMap.get(\"subCmd\"));\n            int merge = CmdKit.merge(cmd, subCmd);\n            var broadcastDocument = this.broadcastDocumentMap.remove(merge);\n            if (Objects.nonNull(broadcastDocument)) {\n                String dataClassName = broadcastDocument.getDataClassName();\n                String description = broadcastDocument.getMethodDescription();\n                String dataDescription = broadcastDocument.getDataDescription();\n                format = StrKit.format(\"    广播推送: {} {}，({})\", dataClassName, dataDescription, description);\n                lineList.add(format);\n            }\n\n            lineList.add(\" \");\n        }\n\n        return String.join(separator, lineList);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocumentAccessAuthentication.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\n/**\n * 文档访问权限生成\n *\n * @author 渔民小镇\n * @date 2024-09-02\n * @since 21.16\n */\npublic interface DocumentAccessAuthentication {\n    /**\n     * 拒绝生成的路由文档，当返回值为 true 时，将不会生成该路由对应的文档\n     *\n     * @param cmdMerge 路由\n     * @return true 表示不会生成该路由对应的文档\n     */\n    boolean reject(int cmdMerge);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocumentAnalyseKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport com.iohao.game.common.kit.CollKit;\nimport com.thoughtworks.qdox.JavaProjectBuilder;\nimport com.thoughtworks.qdox.model.JavaClass;\nimport com.thoughtworks.qdox.model.expression.Expression;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.File;\nimport java.net.URL;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author 渔民小镇\n * @date 2024-06-26\n */\n@Slf4j\n@UtilityClass\nclass DocumentAnalyseKit {\n    List<ActionDocument> analyseActionDocument(IoGameDocument ioGameDocument, TypeMappingDocument typeMappingDocument) {\n        // 数据类型对应的映射值\n        return ioGameDocument.getActionDocList().stream().map(actionDoc -> {\n            // 生成 action 文件\n            ActionDocument actionDocument = new ActionDocument(actionDoc, typeMappingDocument);\n            actionDocument.analyse();\n            return actionDocument;\n        }).filter(actionDocument -> {\n            // action 方法列表不为空\n            List<ActionMethodDocument> actionMethodDocumentList = actionDocument.getActionMethodDocumentList();\n            return !actionMethodDocumentList.isEmpty();\n        }).toList();\n    }\n\n    List<ErrorCodeDocument> analyseErrorCodeDocument(Class<? extends MsgExceptionInfo> clazz) {\n\n        var analyseJavaClassRecord = analyseJavaClass(clazz);\n        if (!analyseJavaClassRecord.exists) {\n            // 框架内置错误码的特殊处理。因为编译后已经没有源码了，所以无法获取框架的 ActionErrorEnum 相关源码。\n            return analyseActionErrorEnumDocument(clazz);\n        }\n\n        return analyseErrorCodeDocument(analyseJavaClassRecord.javaClass());\n    }\n\n    private List<ErrorCodeDocument> analyseActionErrorEnumDocument(Class<? extends MsgExceptionInfo> clazz) {\n\n        if (!ActionErrorEnum.class.equals(clazz)) {\n            return Collections.emptyList();\n        }\n\n        return Arrays.stream(ActionErrorEnum.values()).map(code -> {\n            ErrorCodeDocument errorCodeDocument = new ErrorCodeDocument();\n            errorCodeDocument.setName(code.name());\n            errorCodeDocument.setValue(code.getCode());\n            errorCodeDocument.setDescription(code.getMsg());\n\n            // i18n\n            if (Locale.getDefault() == Locale.US) {\n                errorCodeDocument.setDescription(code.name());\n            }\n\n            return errorCodeDocument;\n        }).toList();\n    }\n\n    AnalyseJavaClassRecord analyseJavaClass(Class<?> clazz) {\n\n        URL resource = clazz.getResource(clazz.getSimpleName() + \".class\");\n        String srcPath = ActionCommandDocKit.sourceFilePathFun.apply(resource).replace(\"class\", \"java\");\n\n        JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();\n\n        File file = new File(srcPath);\n        // 源码在此包才做处理\n        boolean exists = file.exists();\n        if (exists) {\n            javaProjectBuilder.addSourceTree(file);\n        }\n\n        if (!exists && !ActionErrorEnum.class.equals(clazz)) {\n            log.warn(\"无法获取 {} 相关源码\", clazz);\n        }\n\n        JavaClass javaClass = javaProjectBuilder.getClassByName(clazz.getName());\n\n        return new AnalyseJavaClassRecord(exists, javaClass);\n    }\n\n    record AnalyseJavaClassRecord(boolean exists, JavaClass javaClass) {\n\n    }\n\n    private final AtomicInteger gameCodeOrdinal = new AtomicInteger(0);\n\n    private List<ErrorCodeDocument> analyseErrorCodeDocument(JavaClass javaClass) {\n        return javaClass.getFields().stream().map(field -> {\n            List<Expression> enumConstantArguments = field.getEnumConstantArguments();\n            if (CollKit.isEmpty(enumConstantArguments)) {\n                return null;\n            }\n\n            int gameCode = 0;\n            if (enumConstantArguments.size() == 1) {\n                gameCode = gameCodeOrdinal.getAndIncrement();\n            } else if (enumConstantArguments.size() == 2) {\n                Object enumValue = enumConstantArguments.getFirst().getParameterValue();\n                gameCode = Integer.parseInt(enumValue.toString());\n            }\n\n            String name = field.getName();\n\n            ErrorCodeDocument errorCodeDocument = new ErrorCodeDocument();\n            errorCodeDocument.setName(name);\n            errorCodeDocument.setValue(gameCode);\n\n            String description = enumConstantArguments.getLast().getParameterValue().toString();\n            description = description.substring(1, description.length() - 1);\n            errorCodeDocument.setDescription(description);\n\n            return errorCodeDocument;\n        }).filter(Objects::nonNull).toList();\n    }\n\n    ActionCommand.ParamInfo getBizParam(ActionCommand actionCommand) {\n        return actionCommand.streamParamInfo()\n                // 只处理业务参数\n                .filter(ActionCommand.ParamInfo::isBizData)\n                .findAny().orElse(null);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocumentGenerate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\n/**\n * 对接文档生成接口，可扩展不同的实现\n *\n * @author 渔民小镇\n * @date 2024-06-25\n * @since 21.11\n */\npublic interface DocumentGenerate {\n    /**\n     * 生成文档\n     *\n     * @param ioGameDocument ioGameDocument\n     */\n    void generate(IoGameDocument ioGameDocument);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocumentMethod.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport java.lang.annotation.*;\n\n/**\n * Generates the document action method name. By default, the action method name is used.\n *\n * @author 渔民小镇\n * @date 2024-11-12\n * @since 21.20\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface DocumentMethod {\n    /**\n     * The method name of the document action\n     *\n     * @return method name\n     */\n    String value();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ErrorCodeDoc.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\n/**\n * 错误码文档\n *\n * @author 渔民小镇\n * @date 2022-02-03\n */\n@Data\n@Accessors(chain = true)\npublic final class ErrorCodeDoc {\n    /** 异常码 */\n    int code;\n    /** 异常消息 */\n    String msg;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ErrorCodeDocs.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport lombok.Getter;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 错误码文档相关\n *\n * @author 渔民小镇\n * @date 2022-02-03\n * @deprecated 请使用 {@link IoGameDocumentHelper#addErrorCodeClass(Class)}\n */\n@Getter\n@Deprecated\npublic final class ErrorCodeDocs {\n    List<ErrorCodeDoc> errorCodeDocList = new ArrayList<>();\n\n    public void addMsgExceptionInfo(MsgExceptionInfo msgExceptionInfo) {\n        ErrorCodeDoc errorCodeDoc = new ErrorCodeDoc()\n                .setCode(msgExceptionInfo.getCode())\n                .setMsg(msgExceptionInfo.getMsg());\n\n        this.errorCodeDocList.add(errorCodeDoc);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ErrorCodeDocsRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport java.util.*;\n\n/**\n * 错误码域\n *\n * @author 渔民小镇\n * @date 2022-02-03\n * @deprecated 请使用 {@link IoGameDocumentHelper#addErrorCodeClass(Class)}\n */\n@Deprecated\npublic final class ErrorCodeDocsRegion {\n\n    Set<ErrorCodeDoc> errorCodeDocSet = new HashSet<>();\n\n    public void addErrorCodeDocs(ErrorCodeDocs errorCodeDocs) {\n        List<ErrorCodeDoc> errorCodeDocList = errorCodeDocs.getErrorCodeDocList();\n        errorCodeDocSet.addAll(errorCodeDocList);\n    }\n\n    public List<ErrorCodeDoc> listErrorCodeDoc() {\n\n        List<ErrorCodeDoc> list = new ArrayList<>(this.errorCodeDocSet);\n\n        // 排序\n        list.sort((o1, o2) -> o1.code - o2.getCode());\n\n        return list;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/ErrorCodeDocument.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 错误码文档\n *\n * @author 渔民小镇\n * @date 2024-06-26\n */\n@Getter\n@ToString\n@Setter(AccessLevel.PACKAGE)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ErrorCodeDocument {\n    /** 错误码 - 变量名 */\n    String name;\n    /** 错误码 - 值 */\n    int value;\n    /** 错误码 - 描述 */\n    String description;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/IoGameDocument.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport lombok.Getter;\n\nimport java.util.List;\n\n/**\n * 文档相关信息，如 action 相关、广播相关、错误码相关。\n *\n * @author 渔民小镇\n * @date 2024-06-25\n * @since 21.11\n */\n@Getter\npublic final class IoGameDocument {\n    /** 已经解析好的广播文档 */\n    List<BroadcastDocument> broadcastDocumentList;\n    /** 已经解析好的错误码文档 */\n    List<ErrorCodeDocument> errorCodeDocumentList;\n    /** 已经解析好的 action 文档 */\n    List<ActionDoc> actionDocList;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/IoGameDocumentHelper.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.StrKit;\nimport com.thoughtworks.qdox.model.JavaClass;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashMap;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.*;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * IoGameDocumentHelper\n * <p>\n * for example\n * <pre>{@code\n *     // 添加自定义的文档生成器\n *     IoGameDocumentHelper.addDocumentGenerate(new YourDocumentGenerate());\n *     // 添加枚举错误码 class，用于生成错误码相关信息\n *     IoGameDocumentHelper.addErrorCodeClass(YourGameCode.class);\n *     // 生成文档\n *     IoGameDocumentHelper.generateDocument();\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2024-07-05\n * @see DocumentGenerate\n */\n@UtilityClass\npublic class IoGameDocumentHelper {\n    /**\n     * action 文档相关信息\n     * <pre>\n     *      key : action controller\n     *      value : action 文档\n     *  </pre>\n     */\n    private final Map<Class<?>, ActionDoc> actionDocMap = new NonBlockingHashMap<>();\n    /** 错误码枚举类信息，用于生成错误码相关信息 */\n    private final Set<Class<? extends MsgExceptionInfo>> errorCodeClassSet = new NonBlockingHashSet<>();\n    private final Set<DocumentGenerate> documentGenerateSet = new NonBlockingHashSet<>();\n    private final List<BroadcastDocument> broadcastDocumentList = new CopyOnWriteArrayList<>();\n\n    /** true 生成文档 */\n    private boolean generateDoc = true;\n    private boolean once = true;\n    /** 文档路由访问权限控制 */\n    @Getter\n    @Setter\n    private DocumentAccessAuthentication documentAccessAuthentication = cmdMerge -> false;\n\n    /**\n     * 只有当 generateDoc 为 true 时，才会执行 set 操作\n     *\n     * @param generateDoc generateDoc，当为 false 时，将不会生成文档\n     */\n    public void setGenerateDoc(boolean generateDoc) {\n        // 只有当 this.generateDoc 为 true 时，才会执行 set 操作\n        if (IoGameDocumentHelper.generateDoc) {\n            IoGameDocumentHelper.generateDoc = generateDoc;\n        }\n    }\n\n    /**\n     * 对接文档生成\n     */\n    public void generateDocument() {\n        if (!IoGameDocumentHelper.generateDoc || !once) {\n            return;\n        }\n\n        // 只生成一次\n        once = false;\n\n        // 文档解析\n        IoGameDocument ioGameDocument = analyse();\n\n        // 文档生成\n        documentGenerateSet.forEach(documentGenerate -> documentGenerate.generate(ioGameDocument));\n    }\n\n    private IoGameDocument analyse() {\n        // 添加文本文档解析器\n        IoGameDocumentHelper.addDocumentGenerate(new TextDocumentGenerate());\n\n        IoGameDocument ioGameDocument = new IoGameDocument();\n\n        // ------- 错误码解析 -------\n        // 框架默认提供的错误码\n        IoGameDocumentHelper.addErrorCodeClass(ActionErrorEnum.class);\n\n        ioGameDocument.errorCodeDocumentList = IoGameDocumentHelper.errorCodeClassSet\n                .stream()\n                .flatMap(clazz -> DocumentAnalyseKit.analyseErrorCodeDocument(clazz).stream())\n                .sorted(Comparator.comparingInt(ErrorCodeDocument::getValue))\n                .toList();\n\n        // ------- 广播解析 -------\n        ioGameDocument.broadcastDocumentList = IoGameDocumentHelper.broadcastDocumentList;\n        ioGameDocument.broadcastDocumentList.sort(Comparator.comparingInt(BroadcastDocument::getCmdMerge));\n        ioGameDocument.broadcastDocumentList\n                .stream()\n                .filter(broadcastDocument -> Objects.nonNull(broadcastDocument.getDataClass()))\n                .filter(broadcastDocument -> StrKit.isEmpty(broadcastDocument.getDataDescription()))\n                .forEach(broadcastDocument -> {\n                    // 广播业务数据解析，使用类信息的文档注释\n                    Class<?> dataClass = broadcastDocument.getDataClass();\n                    JavaClass javaClass = DocumentAnalyseKit.analyseJavaClass(dataClass).javaClass();\n                    String classComment = javaClass.getComment();\n\n                    broadcastDocument.setDataDescription(classComment);\n                });\n\n        // ------- action 解析 -------\n        ioGameDocument.actionDocList = IoGameDocumentHelper.actionDocMap\n                .values()\n                .stream()\n                .sorted(Comparator\n                        // 先按 cmd 排序\n                        .comparingInt(ActionDoc::getCmd)\n                        // 若 cmd 相同，则按 className 排序\n                        .thenComparing(o -> o.getControllerClazz().getName())\n                ).toList();\n\n        return ioGameDocument;\n    }\n\n    /**\n     * 添加文档生成器，相同类型只能添加一个\n     *\n     * @param documentGenerate 文档生成接口\n     */\n    public void addDocumentGenerate(DocumentGenerate documentGenerate) {\n        documentGenerateSet.add(documentGenerate);\n    }\n\n    /**\n     * 添加枚举错误码 class\n     * <pre>\n     *     参考 {@link com.iohao.game.action.skeleton.core.exception.ActionErrorEnum} 的实现\n     * </pre>\n     *\n     * @param clazz 枚举错误码 class\n     */\n    public void addErrorCodeClass(Class<? extends MsgExceptionInfo> clazz) {\n        IoGameDocumentHelper.errorCodeClassSet.add(clazz);\n    }\n\n    /**\n     * 添加广播文档\n     *\n     * @param broadcastDocument broadcastDocument\n     */\n    public void addBroadcastDocument(BroadcastDocument broadcastDocument) {\n        IoGameDocumentHelper.broadcastDocumentList.add(broadcastDocument);\n    }\n\n    /**\n     * 添加广播文档\n     *\n     * @param broadcastDocumentBuilder broadcastDocumentBuilder\n     */\n    public void addBroadcastDocument(BroadcastDocumentBuilder broadcastDocumentBuilder) {\n        addBroadcastDocument(broadcastDocumentBuilder.build());\n    }\n\n    /**\n     * 获取 ActionDoc，如果 ActionDoc 不存在则创建\n     *\n     * @param cmd             主路由\n     * @param controllerClazz action class\n     * @return 一定不为 null\n     */\n    public ActionDoc ofActionDoc(int cmd, Class<?> controllerClazz) {\n        ActionDoc actionDocRegion = IoGameDocumentHelper.actionDocMap.get(controllerClazz);\n\n        if (Objects.isNull(actionDocRegion)) {\n            return MoreKit.putIfAbsent(actionDocMap, controllerClazz, new ActionDoc(cmd, controllerClazz));\n        }\n\n        return actionDocRegion;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/JavaClassDocInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.StrKit;\nimport com.thoughtworks.qdox.model.DocletTag;\nimport com.thoughtworks.qdox.model.JavaClass;\nimport com.thoughtworks.qdox.model.JavaMethod;\n\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-28\n */\npublic final class JavaClassDocInfo {\n    final JavaClass javaClass;\n    Map<String, JavaMethod> javaMethodMap = new HashMap<>();\n\n    public JavaClassDocInfo(JavaClass javaClass) {\n        this.javaClass = javaClass;\n\n        List<JavaMethod> methods = javaClass.getMethods();\n        for (JavaMethod method : methods) {\n            javaMethodMap.put(method.toString(), method);\n        }\n    }\n\n    public ActionCommandDoc createActionCommandDoc(Method method) {\n        JavaMethod javaMethod = javaMethodMap.get(method.toString());\n        int subCmd = method.getAnnotation(ActionMethod.class).value();\n\n        ActionCommandDoc actionCommandDoc = new ActionCommandDoc();\n        actionCommandDoc.setSubCmd(subCmd);\n        actionCommandDoc.setClassComment(this.javaClass.getComment());\n        actionCommandDoc.setClassLineNumber(this.javaClass.getLineNumber());\n        actionCommandDoc.setComment(javaMethod.getComment());\n        actionCommandDoc.setLineNumber(javaMethod.getLineNumber());\n\n        if (actionCommandDoc.getClassComment() == null) {\n            actionCommandDoc.setClassComment(\"\");\n        }\n\n        if (actionCommandDoc.getComment() == null) {\n            actionCommandDoc.setComment(\"\");\n        }\n\n        methodParamReturnComment(actionCommandDoc, javaMethod);\n\n        return actionCommandDoc;\n    }\n\n    private void methodParamReturnComment(ActionCommandDoc actionCommandDoc, JavaMethod javaMethod) {\n        List<DocletTag> tags = javaMethod.getTags();\n        if (CollKit.isEmpty(tags)) {\n            return;\n        }\n\n        for (DocletTag tag : tags) {\n            String name = tag.getName();\n            String value = tag.getValue();\n\n            if (StrKit.isEmpty(value) || value.contains(\"flowContext\")) {\n                continue;\n            }\n\n            int paramIndex = value.indexOf(\" \");\n            if (\"param\".equals(name) && paramIndex != -1) {\n                String trim = value.substring(paramIndex).trim();\n                actionCommandDoc.setMethodParamComment(trim);\n            } else if (\"return\".equals(name)) {\n                actionCommandDoc.setMethodReturnComment(value);\n            }\n        }\n    }\n\n    public String getComment() {\n        return this.javaClass.getComment();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/TextDocumentGenerate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.common.kit.io.FileKit;\nimport com.iohao.game.common.kit.time.FormatTimeKit;\nimport lombok.Setter;\n\nimport java.io.File;\nimport java.util.*;\n\n/**\n * 文本文档生成\n *\n * @author 渔民小镇\n * @date 2024-06-25\n */\npublic final class TextDocumentGenerate implements DocumentGenerate {\n    private final StringJoiner docContentJoiner = new StringJoiner(System.lineSeparator());\n    /** 文档生成后所存放的目录 */\n    @Setter\n    String path = System.getProperty(\"user.dir\") + File.separator + \"doc_game.txt\";\n\n    @Override\n    public void generate(IoGameDocument ioGameDocument) {\n        // 加上游戏文档格式说明\n        this.gameDocURLDescription();\n\n        Map<Integer, BroadcastDocument> broadcastDocumentMap = new TreeMap<>();\n        ioGameDocument.getBroadcastDocumentList().forEach(broadcastDocument -> {\n            // map put\n            broadcastDocumentMap.put(broadcastDocument.getCmdMerge(), broadcastDocument);\n        });\n\n        // 生成文档 - action\n        ioGameDocument.getActionDocList().forEach(actionDoc -> {\n            var docInfo = new DocInfo();\n\n            actionDoc.stream()\n                    .map(ActionCommandDoc::getActionCommand)\n                    .filter(Objects::nonNull)\n                    .filter(actionCommand -> {\n                        var cmdInfo = actionCommand.getCmdInfo();\n                        var authentication = IoGameDocumentHelper.getDocumentAccessAuthentication();\n                        var cmdMerge = cmdInfo.getCmdMerge();\n                        // 路由访问权限控制\n                        return !authentication.reject(cmdMerge);\n                    }).forEach(subBehavior -> {\n                        docInfo.setHead(subBehavior);\n                        docInfo.add(subBehavior);\n                    });\n\n            if (docInfo.getSubBehaviorList().isEmpty()) {\n                return;\n            }\n\n            docInfo.broadcastDocumentMap = broadcastDocumentMap;\n            String render = docInfo.render();\n            this.docContentJoiner.add(render);\n        });\n\n        // 生成文档 - 广播（推送）文档\n        extractedBroadcastDoc(broadcastDocumentMap);\n\n        // 生成文档 - 错误码文档\n        extractedErrorCode(ioGameDocument);\n\n        // 写文件\n        String docText = this.docContentJoiner.toString();\n        FileKit.writeUtf8String(docText, path);\n    }\n\n    private void gameDocURLDescription() {\n        // 加上游戏文档格式说明\n        String title = Bundle.getMessage(MessageKey.textDocumentTitle);\n\n        String gameDocInfo = \"\"\"\n                ==================== %s ====================\n                https://iohao.github.io/game/docs/examples/code_generate\n                \"\"\".formatted(title);\n\n        this.docContentJoiner.add(\"generate %s\".formatted(FormatTimeKit.format()));\n        this.docContentJoiner.add(gameDocInfo);\n    }\n\n    private void extractedBroadcastDoc(Map<Integer, BroadcastDocument> broadcastDocumentMap) {\n\n        var broadcastDocumentList = broadcastDocumentMap.values();\n        if (broadcastDocumentList.isEmpty()) {\n            return;\n        }\n\n        String title = Bundle.getMessage(MessageKey.textDocumentBroadcastTitle);\n        this.docContentJoiner.add(\"==================== %s ====================\".formatted(title));\n\n\n        for (BroadcastDocument broadcastDocument : broadcastDocumentList) {\n\n            String template = \"{textDocumentCmd}: {cmd} - {subCmd}  --- {textDocumentBroadcast}: {dataClass} {dataDescription}\";\n\n            if (StrKit.isNotEmpty(broadcastDocument.getMethodDescription())) {\n                template = \"{textDocumentCmd}: {cmd} - {subCmd}  --- {textDocumentBroadcast}: {dataClass} {dataDescription}，({description})\";\n            }\n\n            var paramMap = toMap(broadcastDocument);\n\n            String format = StrKit.format(template, paramMap);\n            this.docContentJoiner.add(format);\n        }\n\n        this.docContentJoiner.add(\"\");\n    }\n\n    private HashMap<Object, Object> toMap(BroadcastDocument broadcastDocument) {\n        String textDocumentCmd = Bundle.getMessage(MessageKey.textDocumentCmd);\n        String textDocumentBroadcast = Bundle.getMessage(MessageKey.textDocumentBroadcast);\n\n        var map = new HashMap<>();\n        map.put(\"cmd\", broadcastDocument.getCmd());\n        map.put(\"subCmd\", broadcastDocument.getSubCmd());\n        map.put(\"dataClass\", broadcastDocument.getDataClassName());\n        map.put(\"description\", broadcastDocument.getMethodDescription());\n        map.put(\"dataDescription\", broadcastDocument.getDataDescription());\n        map.put(\"textDocumentCmd\", textDocumentCmd);\n        map.put(\"textDocumentBroadcast\", textDocumentBroadcast);\n\n        return map;\n    }\n\n    private void extractedErrorCode(IoGameDocument ioGameDocument) {\n        String title = Bundle.getMessage(MessageKey.textDocumentErrorCodeTitle);\n\n        this.docContentJoiner.add(\"==================== %s ====================\".formatted(title));\n\n        for (ErrorCodeDocument errorCodeDocument : ioGameDocument.getErrorCodeDocumentList()) {\n            String format = \"%s : %s : %s\".formatted(errorCodeDocument.getValue(),\n                    errorCodeDocument.getDescription(),\n                    errorCodeDocument.getName()\n            );\n\n            this.docContentJoiner.add(format);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/TypeMappingDocument.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.protocol.wrapper.BoolValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.LongValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.StringValue;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 类型映射\n *\n * @author 渔民小镇\n * @date 2024-06-26\n */\npublic interface TypeMappingDocument {\n    List<Class<?>> intClassList = List.of(int.class, Integer.class, IntValue.class);\n    List<Class<?>> longClassList = List.of(long.class, Long.class, LongValue.class);\n    List<Class<?>> boolClassList = List.of(boolean.class, Boolean.class, BoolValue.class);\n    List<Class<?>> stringClassList = List.of(String.class, StringValue.class);\n\n    Map<Class<?>, TypeMappingRecord> getMap();\n\n    TypeMappingRecord getTypeMappingRecord(Class<?> protoTypeClazz);\n\n    default void mapping(TypeMappingRecord record, List<Class<?>> clazzList) {\n        for (Class<?> clazz : clazzList) {\n            this.getMap().put(clazz, record);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/TypeMappingRecord.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 类型映射记录\n *\n * @author 渔民小镇\n * @date 2024-06-26\n */\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class TypeMappingRecord {\n    @Getter\n    String paramTypeName;\n    /** list 参数类型名 */\n    String listParamTypeName;\n    /** sdk 方法名 */\n    String ofMethodTypeName;\n    /** sdk list 参数的方法名 */\n    String ofMethodListTypeName;\n\n    /** sdk result get 方法名 */\n    @Getter\n    String resultMethodTypeName;\n    /** sdk result get list 方法名 */\n    @Getter\n    String resultMethodListTypeName;\n\n    /** 内置扩展类型 */\n    @Getter\n    boolean internalType = true;\n\n    public String getParamTypeName(boolean isList) {\n        return isList ? listParamTypeName : paramTypeName;\n    }\n\n    public String getOfMethodTypeName(boolean isList) {\n        return isList ? ofMethodListTypeName : ofMethodTypeName;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - <a href=\"https://iohao.github.io/game/docs/examples/code_generate\">代码生成</a>，你只需要编写一次 java 代码，就能为 Unity、Godot、CocosCreator、Laya、Vue 等前端项目生成交互接口。\n * <pre>{@code\n * public static void main(String[] args) {\n *     // 添加枚举错误码 class，用于生成错误码相关信息\n *     IoGameDocumentHelper.addErrorCodeClass(GameCode.class);\n *\n *     // 添加代码生成器，生成 C# 联调代码。（21.20 支持）\n *     // IoGameDocumentHelper.addDocumentGenerate(new CsharpDocumentGenerate());\n *     // 添加代码生成器，生成 Ts 联调代码。（21.21 支持）\n *     // IoGameDocumentHelper.addDocumentGenerate(new TypeScriptDocumentGenerate());\n *     // 生成文档\n *     IoGameDocumentHelper.generateDocument();\n * }\n * }</pre>\n * 介绍\n * <pre>\n *     ioGame 是非常注重开发体验的，代码注释即文档、方法即交互接口的原则。\n *\n *     ioGame 具备一次编写到处对接的能力，从而做到了你们团队提升巨大的生产力可能性。\n *     一次编写指的是编写一次 java 业务代码，而到处对接则是指为不同的前端项目生成与服务器交互的代码。\n *\n *     ioGame 能为各种前端项目生成 action、广播、错误码 相关接口代码。\n *     这将意味着，你只需要编写一次业务代码，就可以同时与这些游戏引擎或现代化的前端框架交互。\n * </pre>\n * 代码生成的几个优势\n * <pre>\n *     1. 帮助客户端开发者减少巨大的工作量，不需要编写大量的模板代码。\n *     2. 语义明确，清晰。生成的交互代码即能明确所需要的参数类型，又能明确服务器是否会有返回值。这些会在生成接口时就提前明确好。\n *     3. 由于我们可以做到明确交互接口，进而可以明确参数类型。这使得接口方法参数类型安全、明确，从而有效避免安全隐患，从而减少联调时的低级错误。\n *     4. 减少服务器与客户端双方对接时的沟通成本，代码即文档。生成的联调代码中有文档与使用示例，方法上的示例会教你如何使用，即使是新手也能做到零学习成本。\n *     5. 帮助客户端开发者屏蔽与服务器交互部分，将更多的精力放在真正的业务上。\n *     6. 为双方联调减少心智负担。联调代码使用简单，与本地方法调用一般丝滑。\n *     7. 抛弃传统面向协议对接的方式，转而使用面向接口方法的对接方式。\n *     8. 当我们的 java 代码编写完成后，我们的文档及交互接口可做到同步更新，不需要额外花时间去维护对接文档及其内容。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core.doc;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/enhance/BarSkeletonBuilderEnhance.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.enhance;\n\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilder;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-16\n */\npublic interface BarSkeletonBuilderEnhance {\n    void enhance(BarSkeletonBuilder builder);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/enhance/BarSkeletonBuilderEnhances.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.enhance;\n\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilder;\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.ServiceLoader;\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-16\n */\n@UtilityClass\npublic class BarSkeletonBuilderEnhances {\n\n    final Set<BarSkeletonBuilderEnhance> enhanceSet = new NonBlockingHashSet<>();\n\n    static {\n        ServiceLoader.load(BarSkeletonBuilderEnhance.class).forEach(BarSkeletonBuilderEnhances::add);\n    }\n\n    void add(BarSkeletonBuilderEnhance enhance) {\n        enhanceSet.add(enhance);\n    }\n\n    public void enhance(BarSkeletonBuilder builder) {\n        for (BarSkeletonBuilderEnhance enhance : enhanceSet) {\n            enhance.enhance(builder);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/ActionErrorEnum.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.exception;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Locale;\n\n/**\n * action 错误码\n *\n * @author 渔民小镇\n * @date 2022-01-14\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic enum ActionErrorEnum implements MsgExceptionInfo {\n    /**\n     * 系统其它错误\n     * <pre>\n     *      一般不是用户自定义的异常，很可能是用户引入的第三方包抛出的异常\n     * </pre>\n     */\n    systemOtherErrCode(-1000, \"系统其它错误\"),\n    /** 参数验错误码 */\n    validateErrCode(-1001, \"参数验错误\"),\n    /** 路由错误码，一般是客户端请求了不存在的路由引起的 */\n    cmdInfoErrorCode(-1002, \"路由错误\"),\n    /** 心跳错误码 */\n    idleErrorCode(-1003, \"心跳超时相关\"),\n    /** 需要登录后才能调用业务方法 */\n    verifyIdentity(-1004, \"请先登录\"),\n    /** class 不存在 */\n    classNotExist(-1005, \"class 不存在\"),\n    /** 数据不存在 */\n    dataNotExist(-1006, \"数据不存在\"),\n    /** 强制玩家下线 */\n    forcedOffline(-1007, \"强制玩家下线\"),\n    /**\n     * 查找（访问）绑定的游戏逻辑服不存在\n     * <pre>\n     *     玩家绑定的游戏逻辑服，但是又找不到对应的游戏逻辑服\n     * </pre>\n     */\n    findBindingLogicServerNotExist(-1008, \"绑定的游戏逻辑服不存在\"),\n    ;\n\n    /** 消息码 */\n    final int code;\n    /** 消息模板 */\n    final String msg;\n\n    ActionErrorEnum(int code, String msg) {\n        this.code = code;\n        this.msg = msg;\n    }\n\n    public String getMsg() {\n        return Locale.getDefault() == Locale.CHINA ? msg : name();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/MsgException.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.exception;\n\nimport lombok.Getter;\n\nimport java.io.Serial;\nimport java.util.Objects;\n\n/**\n * 业务框架 异常消息\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic class MsgException extends RuntimeException {\n    @Serial\n    private static final long serialVersionUID = -4977523514509693190L;\n\n    /** 异常消息码 */\n    @Getter\n    final int msgCode;\n    MsgExceptionInfo msgExceptionInfo;\n\n    public MsgException(int msgCode, String message) {\n        super(message);\n        this.msgCode = msgCode;\n    }\n\n    public MsgException(MsgExceptionInfo msgExceptionInfo) {\n        this(msgExceptionInfo.getCode(), msgExceptionInfo.getMsg());\n        this.msgExceptionInfo = msgExceptionInfo;\n    }\n\n    public MsgExceptionInfo getMsgExceptionInfo() {\n        return Objects.isNull(this.msgExceptionInfo)\n                ? this.msgExceptionInfo = new InternalExceptionInfo(msgCode, getMessage())\n                : this.msgExceptionInfo;\n    }\n\n    private record InternalExceptionInfo(int code, String msg) implements MsgExceptionInfo {\n        @Override\n        public String getMsg() {\n            return msg;\n        }\n\n        @Override\n        public int getCode() {\n            return code;\n        }\n    }\n}\n\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/MsgExceptionInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.exception;\n\nimport java.util.Objects;\n\n/**\n * 异常消息\n *\n * @author 渔民小镇\n * @date 2022-01-14\n */\npublic interface MsgExceptionInfo {\n    /**\n     * 异常消息\n     *\n     * @return 消息\n     */\n    String getMsg();\n\n    /**\n     * 异常码\n     *\n     * @return 异常码\n     */\n    int getCode();\n\n    /**\n     * 断言为 true, 就抛出异常\n     *\n     * @param v1 断言值\n     * @throws MsgException e\n     */\n    default void assertTrueThrows(boolean v1) throws MsgException {\n        if (v1) {\n            throw new MsgException(this);\n        }\n    }\n\n    /**\n     * 断言为 true, 就抛出异常\n     *\n     * @param v1  断言值\n     * @param msg 自定义消息\n     * @throws MsgException e\n     */\n    default void assertTrueThrows(boolean v1, String msg) throws MsgException {\n        if (v1) {\n            int code = this.getCode();\n            throw new MsgException(code, msg);\n        }\n    }\n\n    /**\n     * 断言值 value 不能为 null, 否则就抛出异常\n     *\n     * @param value 断言值\n     * @param msg   自定义消息\n     * @throws MsgException e\n     */\n    default void assertNonNull(Object value, String msg) throws MsgException {\n        assertTrue(Objects.nonNull(value), msg);\n    }\n\n    /**\n     * 断言值 value 不能为 null, 否则就抛出异常\n     *\n     * @param value 断言值\n     * @throws MsgException e\n     */\n    default void assertNonNull(Object value) throws MsgException {\n        assertTrue(Objects.nonNull(value));\n    }\n\n    /**\n     * 断言值 value 为 null, 就抛出异常\n     *\n     * @param value 断言值\n     * @throws MsgException e\n     */\n    default void assertNullThrows(Object value) throws MsgException {\n        assertTrueThrows(Objects.isNull(value));\n    }\n\n    /**\n     * 断言值 value 为 null, 就抛出异常\n     *\n     * @param value 断言值\n     * @param msg   自定义消息\n     * @throws MsgException e\n     */\n    default void assertNullThrows(Object value, String msg) throws MsgException {\n        assertTrueThrows(Objects.isNull(value), msg);\n    }\n\n    /**\n     * 断言必须是 true, 否则抛出异常\n     *\n     * @param v1 断言值\n     * @throws MsgException e\n     */\n    default void assertTrue(boolean v1) throws MsgException {\n        if (v1) {\n            return;\n        }\n\n        throw new MsgException(this);\n    }\n\n\n    /**\n     * 断言必须是 false, 否则抛出异常\n     *\n     * @param v1 断言值\n     * @throws MsgException e\n     */\n    default void assertFalse(boolean v1) throws MsgException {\n        this.assertTrue(!v1);\n    }\n\n    /**\n     * 断言必须是 false, 否则抛出异常\n     *\n     * @param v1  断言值\n     * @param msg 自定义消息\n     * @throws MsgException e\n     */\n    default void assertFalse(boolean v1, String msg) throws MsgException {\n        this.assertTrue(!v1, msg);\n    }\n\n\n    /**\n     * 断言必须是 true, 否则抛出异常\n     *\n     * @param v1  断言值\n     * @param msg 自定义消息\n     * @throws MsgException e\n     */\n    default void assertTrue(boolean v1, String msg) throws MsgException {\n        if (v1) {\n            return;\n        }\n\n        int code = this.getCode();\n        throw new MsgException(code, msg);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/MsgExceptionKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.exception;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\n\n/**\n * MsgExceptionKit\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\n@Slf4j\n@UtilityClass\npublic class MsgExceptionKit {\n    /**\n     * 将异常发送给当前用户\n     *\n     * @param e           异常\n     * @param flowContext 当前用户的 flowContext\n     */\n    public void onException(Throwable e, FlowContext flowContext) {\n        Objects.requireNonNull(flowContext);\n\n        ResponseMessage response = flowContext.getResponse();\n\n        if (e instanceof MsgException msgException) {\n            response.setError(msgException.getMsgExceptionInfo());\n        } else {\n            // 系统其它错误，一般不是用户自定义的异常，很可能是用户引入的第三方包抛出的异常\n            response.setError(ActionErrorEnum.systemOtherErrCode);\n            log.error(e.getMessage(), e);\n        }\n\n        response.setData(null);\n        flowContext.broadcastMe(response);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 系统异常全局统一处理，<a href=\"https://iohao.github.io/game/docs/manual/assert_game_code\">断言 + 异常机制 = 清晰简洁的代码</a>\n * <p>\n * 使用示例\n * <pre>{@code\n * // 自定义错误码\n * @Getter\n * public enum GameCodeEnum implements MsgExceptionInfo {\n *     levelMax(202,\"等级超出\"),\n *     ;\n *     // 消息码\n *     final int code;\n *     // 消息\n *     final String msg;\n *\n *     GameCodeEnum(int code, String msg) {\n *         this.code = code;\n *         this.msg = msg;\n *     }\n * }\n *\n * // 使用\n * @ActionController(1)\n * public class DemoAction {\n *     @ActionMethod(1)\n *     public void here(HelloReq helloReq) {\n *         // 断言必须是 true, 否则抛出异常\n *         GameCodeEnum.levelMax.assertTrue(helloReq.level > 10);\n *     }\n * }\n *\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2022-01-14\n */\npackage com.iohao.game.action.skeleton.core.exception;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/ActionAfter.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\n/**\n * ActionAfter 最后的处理，通常用于将数据发送给请求端\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\npublic interface ActionAfter {\n    /**\n     * 最后执行的方法, 一般将发送到客户端的逻辑存放到这里\n     * <pre>\n     * netty\n     *     channelContext.writeAndFlush(msg);\n     * </pre>\n     *\n     * @param flowContext flow 上下文\n     */\n    void execute(FlowContext flowContext);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/ActionMethodExceptionProcess.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\n\n/**\n * ActionMethod 的异常处理\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface ActionMethodExceptionProcess {\n    /**\n     * 异常处理\n     *\n     * @param e e\n     * @return BarException\n     */\n    MsgException processException(Throwable e);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/ActionMethodInOut.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.flow.internal.DebugInOut;\n\n/**\n * inout 接口\n * <pre>\n *     <a href=\"https://iohao.github.io/game/docs/manual/plugin_intro\">插件介绍-文档</a>\n *\n *     {@link ActionCommand} 执行前与执行后的逻辑钩子类\n *\n *     毫无疑问的是这个类的方法名过于刺激，但并不会影响我们的发挥\n *\n *     通过这个接口,你可以做很多事情，当然这要看你的想象力有多丰富了\n *\n *     例如: 日志记录，执行时间打印。 等等 (可参考框架内置的实现类)\n *     see {@link DebugInOut}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\npublic interface ActionMethodInOut {\n\n    /**\n     * fuck前\n     * <pre>\n     *     这个方法不要做耗时计算, 因为是在执行你的业务方法前运行的.\n     *     建议做一些时间记录等非耗时运算\n     * </pre>\n     *\n     * @param flowContext inout 上下文\n     */\n    void fuckIn(FlowContext flowContext);\n\n    /**\n     * fuck后\n     * <pre>\n     *     当执行这个方法时, 已经把响应数据发送到客户端了\n     * </pre>\n     *\n     * @param flowContext inout 上下文\n     */\n    void fuckOut(FlowContext flowContext);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/ActionMethodInvoke.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\n\n/**\n * ActionMethod Invoke\n * <pre>\n *     调用业务层的方法 (即对外提供的方法)\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface ActionMethodInvoke {\n    /**\n     * 具体的业务方法调用\n     * <pre>\n     *     类有上有注解 {@link ActionController}\n     *     方法有注解 {@link ActionMethod}\n     *     只要有这两个注解的，就是业务类\n     * </pre>\n     *\n     * @param flowContext flow 上下文\n     * @return 返回值\n     */\n    Object invoke(FlowContext flowContext);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/ActionMethodParamParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport com.iohao.game.common.consts.CommonConst;\n\n/**\n * action 方法参数解析器 actionCommand\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface ActionMethodParamParser {\n    /** 方法空参数 */\n    Object[] METHOD_PARAMS = CommonConst.emptyObjects;\n\n    /**\n     * 参数解析\n     *\n     * @param flowContext flow 上下文\n     * @return 参数列表 一定不为 null\n     */\n    Object[] listParam(final FlowContext flowContext);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/ActionMethodResultWrap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\n/**\n * ActionMethod 结果包装器\n * <pre>\n *     将 action 业务方法产生的结果或者错误码 包装到响应(ResponseMessage)对象中 。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface ActionMethodResultWrap {\n    /**\n     * 包装结果\n     * <pre>\n     *     将 action 业务方法产生的结果或者错误码 包装到响应(ResponseMessage)对象中 。\n     * </pre>\n     *\n     * @param flowContext flow 上下文\n     */\n    void wrap(FlowContext flowContext);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/FlowContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowOption;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowOptionDynamic;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 业务框架 flow 上下文\n * <pre>\n *     生命周期存在于这一次的 flow 过程\n *\n *     实现了类型明确的动态属性接口 {@link FlowOptionDynamic} ，实现类只需要实现 getOptions 方法就能具有动态属性的功能。\n *     动态属性可以更方便的为 FlowContext 实现属性的扩展，以方便开发者。\n *\n *     扩展属性接口 {@link FlowAttr}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-21\n * @see FlowAttr FlowContext 动态属性\n */\n@Setter\n@Getter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class FlowContext implements SimpleContext {\n    /** 动态属性 */\n    final Map<FlowOption<?>, Object> options = new HashMap<>();\n    /** 业务框架 */\n    BarSkeleton barSkeleton;\n    /** command */\n    ActionCommand actionCommand;\n    /** 控制器类对象 */\n    Object actionController;\n    /** 请求对象 */\n    RequestMessage request;\n    /** 响应对象 */\n    ResponseMessage response;\n    /** 业务方法参数 */\n    Object[] methodParams;\n    /** 业务方法的返回值 */\n    Object methodResult;\n    /** true 业务方法有异常 */\n    boolean error;\n    /** true 执行 ActionAfter 接口 {@link ActionAfter} */\n    boolean executeActionAfter = true;\n    /**\n     * 记录 InOut 插件的开始时间\n     * <pre>\n     *     一般在 InOut 插件 fuckIn 方法中调用\n     *\n     *     由于时间记录会比较常用，所以有必要放到该类中\n     * </pre>\n     */\n    @Setter(AccessLevel.PRIVATE)\n    long inOutStartTime;\n\n    /**\n     * InOut 执行完成后所消耗的时间\n     * <pre>\n     *     一般在 InOut 插件 fuckOut 方法中调用\n     *\n     *     消耗时间 = System.currentTimeMillis - inOutStartTime\n     * </pre>\n     */\n    @Setter(AccessLevel.PRIVATE)\n    long inOutTime;\n\n    /**\n     * 设置响应结果\n     *\n     * @param methodResult 响应结果\n     * @return this\n     */\n    public FlowContext setMethodResult(Object methodResult) {\n\n        if (Objects.nonNull(methodResult)) {\n            this.methodResult = methodResult;\n        }\n\n        return this;\n    }\n\n    @Override\n    public HeadMetadata getHeadMetadata() {\n        return this.request.getHeadMetadata();\n    }\n\n    /**\n     * 开始时间记录，用于 InOut 插件 fuckIn 方法的时间记录\n     * <pre>\n     *     记录 InOut 插件的开始时间\n     *\n     *     由于时间记录会比较常用，所以有必要放到该类中\n     * </pre>\n     */\n    public void inOutStartTime() {\n        if (this.inOutStartTime == 0) {\n            this.inOutStartTime = System.currentTimeMillis();\n        }\n    }\n\n    /**\n     * InOut 执行完成后所消耗的时间\n     * <pre>\n     *     在此之前，确保调用了 {@code this.inOutStartTime()} 方法\n     * </pre>\n     *\n     * @return 消耗时间 = System.currentTimeMillis - inOutStartTime\n     */\n    public long getInOutTime() {\n\n        if (this.inOutStartTime == 0) {\n            // 表示开发者没有主动调用开始的时间记录 inOutStartTime() 方法\n            return Long.MAX_VALUE;\n        }\n\n        if (this.inOutTime == 0) {\n            this.inOutTime = System.currentTimeMillis() - this.inOutStartTime;\n        }\n\n        return inOutTime;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/FlowContextFactory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\n/**\n * 业务框架 flow 上下文 工厂，负责创建 FlowContext\n * <pre>\n *     通过这个工厂，开发者可以自定义 FlowContext 的子类，\n *     通常用于给 FlowContext 子类添加上一些自定义方法\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-08-20\n */\npublic interface FlowContextFactory {\n    /**\n     * 创建业务框架 flow 上下文\n     *\n     * @return FlowContext\n     */\n    FlowContext createFlowContext();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/FlowContextKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.commumication.ChannelContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-03-23\n */\n@UtilityClass\npublic class FlowContextKit {\n\n    /** rpc oneway request */\n    static final byte REQUEST_ONEWAY = (byte) 0x02;\n\n    /**\n     * FlowContext 自身属性赋值\n     *\n     * @param flowContext flowContext\n     */\n    public void employ(FlowContext flowContext) {\n        BarSkeleton barSkeleton = flowContext.getBarSkeleton();\n        // 请求参数\n        RequestMessage request = flowContext.getRequest();\n        // 元信息\n        HeadMetadata headMetadata = request.getHeadMetadata();\n\n        // 路由\n        if (Objects.isNull(flowContext.getActionCommand())) {\n            // 得到路由信息\n            int cmdMerge = headMetadata.getCmdMerge();\n            // 命令域管理器\n            var actionCommandRegions = barSkeleton.getActionCommandRegions();\n            // 根据路由信息得到 ActionCommand\n            var actionCommand = actionCommandRegions.getActionCommand(cmdMerge);\n\n            flowContext.setActionCommand(actionCommand);\n        }\n\n        // 响应对象\n        if (Objects.isNull(flowContext.getResponse())) {\n            // 响应对象创建器\n            var responseMessageCreate = barSkeleton.getResponseMessageCreate();\n            // 创建响应对象\n            var responseMessage = responseMessageCreate.createResponseMessage();\n            request.settingCommonAttr(responseMessage);\n\n            flowContext.setResponse(responseMessage);\n        }\n    }\n\n    public ChannelContext getChannelContext(FlowContext flowContext) {\n        ResponseMessage response = flowContext.getResponse();\n        HeadMetadata headMetadata = response.getHeadMetadata();\n\n        byte rpcCommandType = headMetadata.getRpcCommandType();\n\n        if (rpcCommandType == REQUEST_ONEWAY) {\n            return flowContext.option(FlowAttr.brokerClientContext);\n        } else {\n            return flowContext.option(FlowAttr.channelContext);\n        }\n    }\n\n    public FlowContext ofFlowContext(long userId) {\n        var cmdInfo = CmdInfo.of(0);\n        var requestMessage = BarMessageKit.createRequestMessage(cmdInfo);\n        var headMetadata = requestMessage.getHeadMetadata();\n        headMetadata.setUserId(userId);\n\n        var flowContext = new FlowContext();\n        flowContext.setRequest(requestMessage);\n\n        return flowContext;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/InternalAboutFlowContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport com.iohao.game.action.skeleton.core.*;\nimport com.iohao.game.action.skeleton.core.commumication.*;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowOptionDynamic;\nimport com.iohao.game.action.skeleton.eventbus.EventBus;\nimport com.iohao.game.action.skeleton.eventbus.EventBusMessage;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.action.skeleton.kit.ExecutorSelectKit;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.protocol.collect.ResponseCollectMessage;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalMessage;\nimport com.iohao.game.action.skeleton.protocol.login.SettingUserIdMessage;\nimport com.iohao.game.action.skeleton.protocol.login.SettingUserIdMessageResponse;\nimport com.iohao.game.action.skeleton.protocol.login.SettingUserIdResult;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.common.kit.trace.TraceKit;\nimport lombok.experimental.UtilityClass;\nimport org.slf4j.MDC;\n\nimport java.io.Serializable;\nimport java.util.Collection;\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * FlowContext 能力增强接口\n * <pre>\n *     {@link SimpleAttachment} 元信息相关的能力\n *\n *     {@link SimpleCommon} 动态属性相关的能力\n *     {@link SimpleBarMessageCreator} 创建 barMessage 消息相关的能力\n *     {@link SimpleExecutor} 线程执行器相关的能力\n * </pre>\n * 通信相关的能力\n * <pre>\n *     分布式事件总线相关通信\n *     {@link SimpleCommunicationEventBus}\n *\n *     广播相关通信\n *     {@link SimpleCommunicationBroadcast}\n *\n *     与游戏逻辑服相关通信\n *     {@link SimpleCommunicationInvokeModule}\n *     {@link SimpleCommunicationInvokeModuleVoid}\n *     {@link SimpleCommunicationInvokeModuleCollect}\n *\n *     与游戏对外服相关通信\n *     {@link SimpleCommunicationInvokeExternalModule}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-27\n */\ninterface SimpleContext extends SimpleAttachment\n        // Login（setting userId）\n        , UserIdSetting\n        // 分布式事件总线相关通信\n        , SimpleCommunicationEventBus\n        // 广播相关通信\n        , SimpleCommunicationBroadcast\n        // 与游戏逻辑服相关通信\n        , SimpleCommunicationInvokeModule\n        , SimpleCommunicationInvokeModuleVoid\n        , SimpleCommunicationInvokeModuleCollect\n        // 与游戏对外服相关通信\n        , SimpleCommunicationInvokeExternalModule {\n}\n\n/**\n * 帮助 FlowContext 得到更新、获取元信息的能力\n *\n * @author 渔民小镇\n * @date 2023-12-27\n */\ninterface SimpleAttachment extends SimpleCommunicationInvokeExternalModule {\n    /**\n     * 更新元信息\n     * <pre>\n     *     [同步更新]\n     *\n     *     将元信息更新到玩家所在的游戏对外服中\n     * </pre>\n     *\n     * @param attachment 元信息\n     */\n    default void updateAttachment(final UserAttachment attachment) {\n\n        HeadMetadata headMetadata = this.getHeadMetadata();\n        long userId = headMetadata.getUserId();\n\n        if (userId <= 0) {\n            ThrowKit.ofRuntimeException(\"userId <= 0\");\n        }\n\n        // 将元信息更新到 HeadMetadata 中\n        byte[] headMetadataEncode = DataCodecKit.encode(attachment);\n        headMetadata.setAttachmentData(headMetadataEncode);\n\n        // 根据业务码，调用游戏对外服与业务码对应的业务实现类 （AttachmentExternalBizRegion、ExternalBizCodeCont）\n        int bizCode = IoGameCommonCoreConfig.ExternalBizCode.attachment;\n        this.invokeExternalModuleCollectMessage(bizCode, headMetadataEncode);\n    }\n\n    /**\n     * 更新元信息\n     * <pre>\n     *     [异步更新]\n     *\n     *     将元信息更新到玩家所在的游戏对外服中\n     * </pre>\n     *\n     * @param attachment 元信息\n     */\n    default void updateAttachmentAsync(UserAttachment attachment) {\n        this.getVirtualExecutor().execute(() -> this.updateAttachment(attachment));\n    }\n\n    /**\n     * 更新元信息\n     * <pre>\n     *     [同步更新]\n     *\n     *     将元信息更新到玩家所在的游戏对外服中\n     * </pre>\n     */\n    default void updateAttachment() {\n        UserAttachment attachment = this.getAttachment();\n        this.updateAttachment(attachment);\n    }\n\n    /**\n     * 更新元信息\n     * <pre>\n     *     [异步更新]\n     *\n     *     将元信息更新到玩家所在的游戏对外服中\n     * </pre>\n     */\n    default void updateAttachmentAsync() {\n        this.getVirtualExecutor().execute(this::updateAttachment);\n    }\n\n    /**\n     * 得到元附加信息\n     * <pre>\n     *     一般是在游戏对外服中设置的一些附加信息\n     *     这些信息会跟随请求来到游戏逻辑服中\n     * </pre>\n     *\n     * @param clazz clazz\n     * @param <T>   t\n     * @return 元附加信息\n     */\n    default <T extends UserAttachment> T getAttachment(final Class<T> clazz) {\n        HeadMetadata headMetadata = this.getHeadMetadata();\n        byte[] attachmentData = headMetadata.getAttachmentData();\n        return DataCodecKit.decode(attachmentData, clazz);\n    }\n\n    /**\n     * 得到元附加信息\n     * <p>\n     * example\n     * <pre>{@code\n     *     // 自定义 FlowContext\n     *     public class MyFlowContext extends FlowContext {\n     *         MyAttachment attachment;\n     *\n     *         @Override\n     *         @SuppressWarnings(\"unchecked\")\n     *         public MyAttachment getAttachment() {\n     *\n     *             if (Objects.isNull(attachment)) {\n     *                 this.attachment = this.getAttachment(MyAttachment.class);\n     *             }\n     *\n     *             return this.attachment;\n     *         }\n     *     }\n     *\n     *     // 自定义元信息类\n     *     public class MyAttachment implements Attachment {\n     *         @Getter\n     *         long userId;\n     *     }\n     * }\n     * </pre>\n     *\n     * @param <T> t\n     * @return 元附加信息\n     */\n    default <T extends UserAttachment> T getAttachment() {\n        throw new RuntimeException(\"需要子类实现\");\n    }\n}\n\n/**\n * 帮助 FlowContext 得到通信的能力\n * <pre>\n *     使用此接口通信的目的，更多的是为了得到 FlowContext 中 request HeadMetadata 对象，\n *     并复用 HeadMetadata 部分信息，从而减少一些冗余代码。\n *\n *     在与其他逻辑服通信时，些接口提供了同步、异步、异步回调风格的方法\n *\n *     方法命名规则\n *     同步: invoke_xxx；返回 ResponseMessage 对象\n *     异步: invoke_xxx_Future； 返回 CompletableFuture 对象\n *     异步回调： invoke_xxx_Async； 方法参数提供了回调方法\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-21\n * @see FlowContext\n */\ninterface SimpleCommunication extends SimpleExecutor\n        , SimpleBarMessageCreator {\n\n    /**\n     * 游戏逻辑服\n     * <pre>\n     *     当前 FlowContext 所关联的游戏逻辑服\n     *     BrokerClient\n     * </pre>\n     *\n     * @return 游戏逻辑服\n     */\n    default BrokerClientContext getBrokerClientContext() {\n        return this.option(FlowAttr.brokerClientContext);\n    }\n\n    /**\n     * 框架网络通讯聚合接口\n     *\n     * @return 框架网络通讯聚合接口\n     */\n    private CommunicationAggregationContext aggregationContext() {\n        return this.option(FlowAttr.aggregationContext);\n    }\n\n    /**\n     * 广播通讯上下文\n     *\n     * @return BroadcastContext\n     */\n    default BroadcastContext getBroadcastContext() {\n        return this.aggregationContext();\n    }\n\n    /**\n     * 广播通讯上下文 - 严格顺序的\n     *\n     * @return BroadcastOrderContext\n     */\n    default BroadcastOrderContext getBroadcastOrderContext() {\n        return this.aggregationContext();\n    }\n\n    /**\n     * 游戏逻辑服与游戏逻辑服之间的通讯上下文\n     *\n     * @return InvokeModuleContext\n     */\n    default InvokeModuleContext getInvokeModuleContext() {\n        return this.aggregationContext();\n    }\n\n    /**\n     * 游戏逻辑服与游戏对外服的通讯上下文\n     *\n     * @return InvokeExternalModuleContext\n     */\n    default InvokeExternalModuleContext getInvokeExternalModuleContext() {\n        return this.aggregationContext();\n    }\n\n    default <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {\n        return CompletableFuture.supplyAsync(supplier, this.getVirtualExecutor());\n    }\n}\n\n/**\n * 帮助 FlowContext 得到与其他游戏逻辑服通信的能力（模块之间的访问，访问【同类型】的多个逻辑服）\n */\ninterface SimpleCommunicationInvokeModuleCollect extends SimpleCommunication {\n\n    /**\n     * [同步] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @return ResponseCollectMessage\n     */\n    default ResponseCollectMessage invokeModuleCollectMessage(CmdInfo cmdInfo) {\n        return invokeModuleCollectMessage(cmdInfo, null);\n    }\n\n    /**\n     * [同步] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param cmdInfo 路由信息\n     * @param data    业务数据\n     * @return ResponseCollectMessage\n     */\n    default ResponseCollectMessage invokeModuleCollectMessage(CmdInfo cmdInfo, Object data) {\n        var requestMessage = createRequestMessage(cmdInfo, data);\n        return invokeModuleCollectMessage(requestMessage);\n    }\n\n    /**\n     * [同步] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param requestMessage requestMessage\n     * @return ResponseCollectMessage\n     */\n    default ResponseCollectMessage invokeModuleCollectMessage(RequestMessage requestMessage) {\n        var invokeModuleContext = this.getInvokeModuleContext();\n        return invokeModuleContext.invokeModuleCollectMessage(requestMessage);\n    }\n\n    /**\n     * [异步] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @return CompletableFuture ResponseCollectMessage\n     */\n    default CompletableFuture<ResponseCollectMessage> invokeModuleCollectMessageFuture(CmdInfo cmdInfo) {\n        return this.invokeModuleCollectMessageFuture(cmdInfo, null);\n    }\n\n    /**\n     * [异步] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @param data    业务数据\n     * @return CompletableFuture ResponseCollectMessage\n     */\n    default CompletableFuture<ResponseCollectMessage> invokeModuleCollectMessageFuture(CmdInfo cmdInfo, Object data) {\n        var requestMessage = createRequestMessage(cmdInfo, data);\n        return this.invokeModuleCollectMessageFuture(requestMessage);\n    }\n\n    /**\n     * [异步] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param requestMessage requestMessage\n     * @return CompletableFuture ResponseCollectMessage\n     */\n    default CompletableFuture<ResponseCollectMessage> invokeModuleCollectMessageFuture(RequestMessage requestMessage) {\n        return this.supplyAsync(() -> this.invokeModuleCollectMessage(requestMessage));\n    }\n\n    /**\n     * [异步回调] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param cmdInfo  路由\n     * @param callback 异步回调方法\n     */\n    default void invokeModuleCollectMessageAsync(CmdInfo cmdInfo, Consumer<ResponseCollectMessage> callback) {\n        this.invokeModuleCollectMessageAsync(cmdInfo, null, callback);\n    }\n\n    /**\n     * [异步回调] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param cmdInfo  路由\n     * @param data     业务数据\n     * @param callback 异步回调方法\n     */\n    default void invokeModuleCollectMessageAsync(CmdInfo cmdInfo, Object data, Consumer<ResponseCollectMessage> callback) {\n        var requestMessage = this.createRequestMessage(cmdInfo, data);\n        this.invokeModuleCollectMessageAsync(requestMessage, callback);\n    }\n\n    /**\n     * [异步回调] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param requestMessage requestMessage\n     * @param callback       异步回调方法\n     */\n    default void invokeModuleCollectMessageAsync(RequestMessage requestMessage, Consumer<ResponseCollectMessage> callback) {\n        this.invokeModuleCollectMessageAsync(requestMessage, callback, this.getExecutor());\n    }\n\n    /**\n     * [异步回调] 模块之间的访问，访问【同类型】的多个逻辑服\n     * <pre>\n     *     模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是游戏逻辑服。\n     *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n     *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n     * </pre>\n     *\n     * @param requestMessage   requestMessage\n     * @param callback         异步回调方法\n     * @param callbackExecutor 处理回调的 Executor\n     */\n    default void invokeModuleCollectMessageAsync(final RequestMessage requestMessage\n            , final Consumer<ResponseCollectMessage> callback, final Executor callbackExecutor) {\n\n        var headMetadata = requestMessage.getHeadMetadata();\n        var traceId = headMetadata.getTraceId();\n\n        if (Objects.isNull(traceId)) {\n            this.invokeModuleCollectMessageFuture(requestMessage).thenAcceptAsync(callback, callbackExecutor);\n            return;\n        }\n\n        this.invokeModuleCollectMessageFuture(requestMessage).thenAcceptAsync(responseCollectMessage -> {\n            // 简单装饰\n            InternalFlowContextKit.decorate(traceId, callback, responseCollectMessage);\n        }, callbackExecutor);\n    }\n}\n\n/**\n * 帮助 FlowContext 得到广播通信的能力\n */\ninterface SimpleCommunicationBroadcast extends SimpleCommunication {\n    private void employTraceId(ResponseMessage responseMessage) {\n        String traceId = this.getHeadMetadata().getTraceId();\n\n        if (Objects.nonNull(traceId)) {\n            HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n            headMetadata.setTraceId(traceId);\n        }\n    }\n\n    private void extractedSourceClientId(ResponseMessage responseMessage, long userId) {\n        var responseMessageHeadMetadata = responseMessage.getHeadMetadata();\n        if (responseMessageHeadMetadata.getSourceClientId() != 0) {\n            // 说明已经指定了需要精准广播的游戏对外服\n            return;\n        }\n\n        // userId 等于自己，这里就精准广播到玩家所在的对外服中（即使启动了多个游戏对外服，也能精准到玩家所在的对外服中）\n        var headMetadata = this.getHeadMetadata();\n        if (userId != headMetadata.getUserId()) {\n            return;\n        }\n\n        // 指定游戏对外服广播（当前玩家所在的游戏对外服）\n        var sourceClientId = headMetadata.getSourceClientId();\n        responseMessageHeadMetadata.setSourceClientId(sourceClientId);\n    }\n\n    /**\n     * 给自己发送消息\n     * <pre>\n     *     路由则使用当前 action 的路由。\n     * </pre>\n     *\n     * @param bizData 业务数据\n     * @see HeadMetadata#getCmdInfo()\n     */\n    default void broadcastMe(Object bizData) {\n        var cmdInfo = this.getCmdInfo();\n        this.broadcastMe(cmdInfo, bizData);\n    }\n\n    /**\n     * 给自己发送消息\n     *\n     * @param cmdInfo 发送到此路由\n     * @param bizData 业务数据\n     */\n    default void broadcastMe(CmdInfo cmdInfo, Object bizData) {\n        var responseMessage = this.createResponseMessage(cmdInfo, bizData);\n        this.broadcastMe(responseMessage);\n    }\n\n    /**\n     * 给自己发送消息\n     *\n     * @param responseMessage 消息\n     */\n    default void broadcastMe(ResponseMessage responseMessage) {\n        var userId = this.getUserId();\n        if (userId == 0) {\n            ThrowKit.ofRuntimeException(Bundle.getMessage(MessageKey.bindingUserId));\n        }\n\n        this.broadcast(responseMessage, userId);\n    }\n\n    /**\n     * 全服广播\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     */\n    default void broadcast(CmdInfo cmdInfo, Object bizData) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcast(responseMessage);\n    }\n\n    /**\n     * 全服广播\n     *\n     * @param responseMessage 消息\n     */\n    default void broadcast(ResponseMessage responseMessage) {\n        employTraceId(responseMessage);\n\n        BroadcastContext broadcastContext = this.getBroadcastContext();\n        broadcastContext.broadcast(responseMessage);\n    }\n\n    /**\n     * 广播消息给单个用户\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     * @param userId  userId\n     */\n    default void broadcast(CmdInfo cmdInfo, Object bizData, long userId) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcast(responseMessage, userId);\n    }\n\n    /**\n     * 广播消息给单个用户\n     *\n     * @param responseMessage 消息\n     * @param userId          userId\n     */\n    default void broadcast(final ResponseMessage responseMessage, final long userId) {\n\n        employTraceId(responseMessage);\n\n        extractedSourceClientId(responseMessage, userId);\n\n        BroadcastContext broadcastContext = this.getBroadcastContext();\n        broadcastContext.broadcast(responseMessage, userId);\n    }\n\n    /**\n     * 广播消息给指定用户列表\n     *\n     * @param cmdInfo    广播到此路由\n     * @param bizData    业务数据\n     * @param userIdList 指定用户列表\n     */\n    default void broadcast(CmdInfo cmdInfo, Object bizData, Collection<Long> userIdList) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcast(responseMessage, userIdList);\n    }\n\n    /**\n     * 广播消息给指定用户列表\n     *\n     * @param responseMessage 消息\n     * @param userIdList      指定用户列表 (如果为 null 或 empty 就不会触发)\n     */\n    default void broadcast(final ResponseMessage responseMessage, final Collection<Long> userIdList) {\n        employTraceId(responseMessage);\n\n        BroadcastContext broadcastContext = this.getBroadcastContext();\n        broadcastContext.broadcast(responseMessage, userIdList);\n    }\n\n    /**\n     * 顺序 - 给自己发送消息\n     * <pre>\n     *     路由则使用当前 action 的路由。\n     * </pre>\n     *\n     * @param bizData 业务数据\n     * @see HeadMetadata#getCmdInfo()\n     */\n    default void broadcastOrderMe(Object bizData) {\n        var cmdInfo = this.getCmdInfo();\n        this.broadcastOrderMe(cmdInfo, bizData);\n    }\n\n    /**\n     * 顺序 - 给自己发送消息\n     *\n     * @param cmdInfo 发送到此路由\n     * @param bizData 业务数据\n     */\n    default void broadcastOrderMe(CmdInfo cmdInfo, Object bizData) {\n        var responseMessage = this.createResponseMessage(cmdInfo, bizData);\n        this.broadcastOrderMe(responseMessage);\n    }\n\n    /**\n     * 顺序 - 给自己发送消息\n     *\n     * @param responseMessage 消息\n     */\n    default void broadcastOrderMe(ResponseMessage responseMessage) {\n        var userId = this.getUserId();\n        this.broadcastOrder(responseMessage, userId);\n    }\n\n    /**\n     * 顺序 - 全服广播\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     */\n    default void broadcastOrder(CmdInfo cmdInfo, Object bizData) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcastOrder(responseMessage);\n    }\n\n    /**\n     * 顺序 - 全服广播\n     *\n     * @param responseMessage 消息\n     */\n    default void broadcastOrder(final ResponseMessage responseMessage) {\n        employTraceId(responseMessage);\n\n        BroadcastOrderContext broadcastOrderContext = this.getBroadcastOrderContext();\n        broadcastOrderContext.broadcastOrder(responseMessage);\n    }\n\n    /**\n     * 顺序 - 广播消息给指定用户列表\n     *\n     * @param cmdInfo    广播到此路由\n     * @param bizData    业务数据\n     * @param userIdList 指定用户列表\n     */\n    default void broadcastOrder(CmdInfo cmdInfo, Object bizData, Collection<Long> userIdList) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcastOrder(responseMessage, userIdList);\n    }\n\n    /**\n     * 顺序 - 广播消息给指定用户列表\n     *\n     * @param responseMessage 消息\n     * @param userIdList      指定用户列表 (如果为 null 或 empty 就不会触发)\n     */\n    default void broadcastOrder(ResponseMessage responseMessage, Collection<Long> userIdList) {\n        employTraceId(responseMessage);\n\n        BroadcastOrderContext broadcastOrderContext = this.getBroadcastOrderContext();\n        broadcastOrderContext.broadcastOrder(responseMessage, userIdList);\n    }\n\n    /**\n     * 顺序 - 广播消息给单个用户\n     *\n     * @param cmdInfo 广播到此路由\n     * @param bizData 业务数据\n     * @param userId  userId\n     */\n    default void broadcastOrder(CmdInfo cmdInfo, Object bizData, long userId) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        this.broadcastOrder(responseMessage, userId);\n    }\n\n    /**\n     * 顺序 - 广播消息给单个用户\n     *\n     * @param responseMessage 消息\n     * @param userId          userId\n     */\n    default void broadcastOrder(final ResponseMessage responseMessage, final long userId) {\n        employTraceId(responseMessage);\n\n        extractedSourceClientId(responseMessage, userId);\n\n        BroadcastOrderContext broadcastOrderContext = this.getBroadcastOrderContext();\n        broadcastOrderContext.broadcastOrder(responseMessage, userId);\n    }\n\n}\n\n/**\n * 帮助 FlowContext 得到与游戏对外服通信的能力\n */\ninterface SimpleCommunicationInvokeExternalModule extends SimpleCommunication {\n\n    /**\n     * [同步] 向玩家所在的游戏对外服请求数据。\n     *\n     * @param bizCode bizCode\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    default ResponseCollectExternalMessage invokeExternalModuleCollectMessage(int bizCode) {\n        return this.invokeExternalModuleCollectMessage(bizCode, null);\n    }\n\n    /**\n     * [同步] 向玩家所在的游戏对外服请求数据。\n     *\n     * @param bizCode bizCode\n     * @param data    业务数据\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    default ResponseCollectExternalMessage invokeExternalModuleCollectMessage(int bizCode, Serializable data) {\n        var request = createRequestCollectExternalMessage(bizCode, data);\n\n        this.extractedSourceClientId(request);\n\n        return this.invokeExternalModuleCollectMessage(request);\n    }\n\n    private void extractedSourceClientId(RequestCollectExternalMessage request) {\n        // 强制指定需要访问的游戏对外服；当指定 id 后，将不会访问所有的游戏对外服\n        var headMetadata = this.getHeadMetadata();\n        request.setSourceClientId(headMetadata.getSourceClientId());\n    }\n\n    /**\n     * 创建 RequestCollectExternalMessage，会为 {@link RequestCollectExternalMessage} 添加 userId、traceId 相关信息\n     *\n     * @param bizCode 业务码\n     * @return RequestCollectExternalMessage\n     */\n    default RequestCollectExternalMessage createRequestCollectExternalMessage(int bizCode) {\n        return this.createRequestCollectExternalMessage(bizCode, null);\n    }\n\n    /**\n     * 创建 RequestCollectExternalMessage，会为 {@link RequestCollectExternalMessage} 添加 userId、traceId 相关信息\n     *\n     * @param bizCode 业务码\n     * @param data    业务数据\n     * @return RequestCollectExternalMessage\n     */\n    default RequestCollectExternalMessage createRequestCollectExternalMessage(int bizCode, Serializable data) {\n        // 得到发起请求的游戏对外服 id\n        var headMetadata = this.getHeadMetadata();\n\n        return new RequestCollectExternalMessage()\n                // 根据业务码，调用游戏对外服与业务码对应的业务实现类\n                .setBizCode(bizCode)\n                // 业务数据\n                .setData(data)\n                // userId、traceId\n                .setUserId(headMetadata.getUserId())\n                .setTraceId(headMetadata.getTraceId())\n                ;\n    }\n\n    /**\n     * [同步] 访问游戏对外服，会为 {@link RequestCollectExternalMessage} 添加 userId、traceId 相关信息，\n     * 如果 request 没有指定 sourceClientId，将会访问所有的游戏对外服。\n     *\n     * @param request request\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    default ResponseCollectExternalMessage invokeExternalModuleCollectMessage(RequestCollectExternalMessage request) {\n        // MDC\n        if (Objects.isNull(request.getTraceId())) {\n            var traceId = this.getHeadMetadata().getTraceId();\n            request.setTraceId(traceId);\n        }\n\n        if (request.getUserId() == 0) {\n            long userId = this.getUserId();\n            request.setUserId(userId);\n        }\n\n        // 【游戏逻辑服】与【游戏对外服】通讯上下文\n        var invokeExternalModuleContext = this.getInvokeExternalModuleContext();\n        return invokeExternalModuleContext.invokeExternalModuleCollectMessage(request);\n    }\n\n    /**\n     * [异步] 向玩家所在的游戏对外服请求数据。\n     *\n     * @param bizCode bizCode\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    default CompletableFuture<ResponseCollectExternalMessage> invokeExternalModuleCollectMessageFuture(int bizCode) {\n        return this.invokeExternalModuleCollectMessageFuture(bizCode, null);\n    }\n\n    /**\n     * [异步] 向玩家所在的游戏对外服请求数据。\n     *\n     * @param bizCode bizCode\n     * @param data    业务数据\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    default CompletableFuture<ResponseCollectExternalMessage> invokeExternalModuleCollectMessageFuture(\n            int bizCode, Serializable data) {\n\n        RequestCollectExternalMessage request = this.createRequestCollectExternalMessage(bizCode, data);\n\n        this.extractedSourceClientId(request);\n\n        return this.invokeExternalModuleCollectMessageFuture(request);\n    }\n\n    /**\n     * [异步] 访问游戏对外服，会为 {@link RequestCollectExternalMessage} 添加 userId、traceId 相关信息，\n     * 如果 request 没有指定 sourceClientId，将会访问所有的游戏对外服。\n     *\n     * @param request request\n     * @return ResponseCollectExternalMessage 一定不为 null\n     */\n    default CompletableFuture<ResponseCollectExternalMessage> invokeExternalModuleCollectMessageFuture(\n            RequestCollectExternalMessage request) {\n\n        return this.supplyAsync(() -> this.invokeExternalModuleCollectMessage(request));\n    }\n\n    /**\n     * [异步回调] 向玩家所在的游戏对外服请求数据。\n     *\n     * @param bizCode  bizCode\n     * @param callback 异步回调方法\n     */\n    default void invokeExternalModuleCollectMessageAsync(int bizCode\n            , Consumer<ResponseCollectExternalMessage> callback) {\n\n        this.invokeExternalModuleCollectMessageAsync(bizCode, null, callback);\n    }\n\n    /**\n     * [异步回调] 向玩家所在的游戏对外服请求数据。\n     *\n     * @param bizCode  bizCode\n     * @param data     业务数据\n     * @param callback 异步回调方法\n     */\n    default void invokeExternalModuleCollectMessageAsync(int bizCode\n            , Serializable data, Consumer<ResponseCollectExternalMessage> callback) {\n\n        RequestCollectExternalMessage request = this.createRequestCollectExternalMessage(bizCode, data);\n\n        this.extractedSourceClientId(request);\n\n        this.invokeExternalModuleCollectMessageAsync(request, callback);\n    }\n\n    /**\n     * [异步回调] 向玩家所在的游戏对外服请求数据。\n     *\n     * @param request  request\n     * @param callback 异步回调方法\n     */\n    default void invokeExternalModuleCollectMessageAsync(RequestCollectExternalMessage request\n            , Consumer<ResponseCollectExternalMessage> callback) {\n\n        this.invokeExternalModuleCollectMessageAsync(request, callback, this.getExecutor());\n    }\n\n    /**\n     * [异步回调] 访问游戏对外服，如果 {@link RequestCollectExternalMessage} 没有指定 sourceClientId，\n     * 将会访问所有的游戏对外服。\n     *\n     * @param request          request\n     * @param callback         异步回调方法\n     * @param callbackExecutor 处理回调的 Executor\n     */\n    default void invokeExternalModuleCollectMessageAsync(final RequestCollectExternalMessage request\n            , final Consumer<ResponseCollectExternalMessage> callback, final Executor callbackExecutor) {\n\n        var traceId = request.getTraceId();\n\n        if (Objects.isNull(traceId)) {\n            this.invokeExternalModuleCollectMessageFuture(request).thenAcceptAsync(callback, callbackExecutor);\n            return;\n        }\n\n        this.invokeExternalModuleCollectMessageFuture(request).thenAcceptAsync(responseCollectExternalMessage -> {\n            // 简单装饰\n            InternalFlowContextKit.decorate(traceId, callback, responseCollectExternalMessage);\n        }, callbackExecutor);\n    }\n}\n\n/**\n * 帮助 FlowContext 得到与其他游戏逻辑服通信的能力\n */\ninterface SimpleCommunicationInvokeModuleVoid extends SimpleCommunication {\n\n    /**\n     * [异步，通知] 请求其他游戏逻辑服的方法，通知方并不关心被调用方是否执行完成。\n     * example\n     * <pre>{@code\n     *     // 内部模块通讯上下文，内部模块指的是游戏逻辑服\n     *     InvokeModuleContext invokeModuleContext = ...\n     *     // 请求房间逻辑服来创建房间，并且不需要返回值\n     *     // 路由、业务参数\n     *     invokeModuleContext.invokeModuleVoidMessage(cmdInfo);\n     * }\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     */\n    default void invokeModuleVoidMessage(CmdInfo cmdInfo) {\n        this.invokeModuleVoidMessage(cmdInfo, null);\n    }\n\n    /**\n     * [异步，通知] 请求其他游戏逻辑服的方法，通知方并不关心被调用方是否执行完成。\n     * example\n     * <pre>{@code\n     *     // 内部模块通讯上下文，内部模块指的是游戏逻辑服\n     *     InvokeModuleContext invokeModuleContext = ...\n     *     // 请求房间逻辑服来创建房间，并且不需要返回值\n     *     // 路由、业务参数\n     *     invokeModuleContext.invokeModuleVoidMessage(cmdInfo, data);\n     * }\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @param data    业务数据\n     */\n    default void invokeModuleVoidMessage(CmdInfo cmdInfo, Object data) {\n        var requestMessage = this.createRequestMessage(cmdInfo, data);\n        this.invokeModuleVoidMessage(requestMessage);\n    }\n\n    /**\n     * [异步，通知] 请求其他游戏逻辑服的方法，通知方并不关心被调用方是否执行完成。\n     * example\n     * <pre>{@code\n     *     // 内部模块通讯上下文，内部模块指的是游戏逻辑服\n     *     InvokeModuleContext invokeModuleContext = ...\n     *     // 请求房间逻辑服来创建房间，并且不需要返回值\n     *     // 路由、业务参数\n     *     invokeModuleContext.invokeModuleVoidMessage(requestMessage);\n     * }\n     * </pre>\n     *\n     * @param requestMessage requestMessage\n     */\n    default void invokeModuleVoidMessage(RequestMessage requestMessage) {\n        var invokeModuleContext = this.getInvokeModuleContext();\n        invokeModuleContext.invokeModuleVoidMessage(requestMessage);\n    }\n}\n\n/**\n * 帮助 FlowContext 得到与其他游戏逻辑服通信的能力\n */\ninterface SimpleCommunicationInvokeModule extends SimpleCommunication {\n\n    /**\n     * [同步] 请求其他游戏逻辑服的数据。\n     *\n     * @param cmdInfo 路由\n     * @return ResponseMessage\n     */\n    default ResponseMessage invokeModuleMessage(CmdInfo cmdInfo) {\n        return this.invokeModuleMessage(cmdInfo, null);\n    }\n\n    /**\n     * [同步] 请求其他游戏逻辑服的数据。\n     *\n     * @param cmdInfo cmdInfo\n     * @param data    请求参数\n     * @return ResponseMessage\n     */\n    default ResponseMessage invokeModuleMessage(CmdInfo cmdInfo, Object data) {\n        var requestMessage = this.createRequestMessage(cmdInfo, data);\n        return invokeModuleMessage(requestMessage);\n    }\n\n    /**\n     * [同步] 请求其他游戏逻辑服的数据。\n     *\n     * @param requestMessage requestMessage\n     * @return ResponseMessage\n     */\n    default ResponseMessage invokeModuleMessage(RequestMessage requestMessage) {\n        var invokeModuleContext = this.getInvokeModuleContext();\n        return invokeModuleContext.invokeModuleMessage(requestMessage);\n    }\n\n    /**\n     * [异步] 请求其他游戏逻辑服的数据。\n     *\n     * @param cmdInfo 路由\n     * @return CompletableFuture ResponseMessage\n     */\n    default CompletableFuture<ResponseMessage> invokeModuleMessageFuture(CmdInfo cmdInfo) {\n        return invokeModuleMessageFuture(cmdInfo, null);\n    }\n\n    /**\n     * [异步] 请求其他游戏逻辑服的数据。\n     *\n     * @param cmdInfo 路由\n     * @param data    业务数据\n     * @return CompletableFuture ResponseMessage\n     */\n    default CompletableFuture<ResponseMessage> invokeModuleMessageFuture(CmdInfo cmdInfo, Object data) {\n        var requestMessage = this.createRequestMessage(cmdInfo, data);\n        return invokeModuleMessageFuture(requestMessage);\n    }\n\n    /**\n     * [异步] 请求其他游戏逻辑服的数据。\n     *\n     * @param requestMessage requestMessage\n     * @return CompletableFuture ResponseMessage\n     */\n    default CompletableFuture<ResponseMessage> invokeModuleMessageFuture(RequestMessage requestMessage) {\n        return this.supplyAsync(() -> this.invokeModuleMessage(requestMessage));\n    }\n\n    /**\n     * [异步回调] 请求其他游戏逻辑服的数据。\n     *\n     * @param cmdInfo  路由\n     * @param callback 异步回调方法\n     */\n    default void invokeModuleMessageAsync(CmdInfo cmdInfo, Consumer<ResponseMessage> callback) {\n        this.invokeModuleMessageAsync(cmdInfo, null, callback);\n    }\n\n    /**\n     * [异步回调] 请求其他游戏逻辑服的数据。\n     *\n     * @param cmdInfo  路由\n     * @param data     业务数据\n     * @param callback 异步回调方法\n     */\n    default void invokeModuleMessageAsync(CmdInfo cmdInfo, Object data, Consumer<ResponseMessage> callback) {\n        var requestMessage = this.createRequestMessage(cmdInfo, data);\n        this.invokeModuleMessageAsync(requestMessage, callback);\n    }\n\n    /**\n     * [异步回调] 请求其他游戏逻辑服的数据。\n     *\n     * @param requestMessage requestMessage\n     * @param callback       异步回调方法\n     */\n    default void invokeModuleMessageAsync(RequestMessage requestMessage, Consumer<ResponseMessage> callback) {\n        this.invokeModuleMessageAsync(requestMessage, callback, this.getExecutor());\n    }\n\n    /**\n     * [异步回调] 请求其他游戏逻辑服的数据。\n     *\n     * @param requestMessage   requestMessage\n     * @param callback         异步回调方法\n     * @param callbackExecutor 处理回调的 Executor\n     */\n    default void invokeModuleMessageAsync(final RequestMessage requestMessage\n            , final Consumer<ResponseMessage> callback, final Executor callbackExecutor) {\n\n        var headMetadata = requestMessage.getHeadMetadata();\n        var traceId = headMetadata.getTraceId();\n\n        if (Objects.isNull(traceId)) {\n            this.invokeModuleMessageFuture(requestMessage).thenAcceptAsync(callback, callbackExecutor);\n            return;\n        }\n\n        this.invokeModuleMessageFuture(requestMessage).thenAcceptAsync(responseMessage -> {\n            // 简单装饰\n            InternalFlowContextKit.decorate(traceId, callback, responseMessage);\n        }, callbackExecutor);\n    }\n}\n\n/**\n * 帮助 FlowContext 得到通信的能力（事件发布）\n */\ninterface SimpleCommunicationEventBus extends SimpleCommunication {\n    /**\n     * EventBus 是逻辑服事件总线。 EventBus、业务框架、逻辑服三者是 1:1:1 的关系。\n     *\n     * @return EventBus\n     */\n    default EventBus getEventBus() {\n        return this.getBarSkeleton().option(SkeletonAttr.eventBus);\n    }\n\n    /**\n     * [异步] 发送事件给所有订阅者\n     * <pre>\n     *     1 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 给其他进程的订阅者发送事件消息\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fire(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fire(eventBusMessage);\n    }\n\n    /**\n     * [同步] 发送事件给所有订阅者\n     * <pre>\n     *     1 [同步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 [异步] 给其他进程的订阅者发送事件消息\n     *\n     *     注意，这里的同步仅指当前进程订阅者的同步，对其他进程中的订阅者无效（处理远程订阅者使用的是异步）。\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireSync(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fireSync(eventBusMessage);\n    }\n\n    /**\n     * [异步] 给当前进程的订阅者和远程进程的订阅者送事件消息，如果同类型逻辑服存在多个，只会给其中一个实例发送。\n     * <pre>\n     *     1 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 给其他进程的订阅者发送事件消息\n     * </pre>\n     * 使用场景\n     * <pre>\n     *     假设现在有一个发放奖励的邮件逻辑服，我们启动了两个（或者说多个）邮件逻辑服实例来处理业务。\n     *     当我们使用 fireAny 方法发送事件时，只会给其中一个实例发送事件。\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireAny(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fireAny(eventBusMessage);\n    }\n\n    /**\n     * [同步] 给当前进程的订阅者和远程进程的订阅者送事件消息，如果同类型逻辑服存在多个，只会给其中一个实例发送。\n     * <p>\n     * 这里的同类型指的是相同类型的逻辑服，也就是拥有相同 tag 的逻辑服。\n     * <pre>\n     *     1 [同步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 [异步] 给其他进程的订阅者发送事件消息\n     *\n     *     注意，这里的同步仅指当前进程订阅者的同步，对其他进程中的订阅者无效（处理远程订阅者使用的是异步）。\n     * </pre>\n     * 使用场景\n     * <pre>\n     *     假设现在有一个发放奖励的邮件逻辑服，我们启动了两个（或者说多个）邮件逻辑服实例来处理业务。\n     *     当我们使用 fireAny 方法发送事件时，只会给其中一个实例发送事件。\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireAnySync(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fireAnySync(eventBusMessage);\n    }\n\n    /**\n     * [异步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *\n     * @param eventSource 事件源\n     */\n    default void fireLocal(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fireLocal(eventBusMessage);\n    }\n\n    /**\n     * [同步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *\n     * @param eventSource 事件源\n     */\n    default void fireLocalSync(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fireLocalSync(eventBusMessage);\n    }\n\n    /**\n     * [异步] 仅给当前 EventBus 的订阅者发送事件消息\n     *\n     * @param eventSource 事件源\n     */\n    default void fireMe(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fireMe(eventBusMessage);\n    }\n\n    /**\n     * [同步] 仅给当前 EventBus 的订阅者发送事件消息\n     *\n     * @param eventSource 事件源\n     */\n    default void fireMeSync(Object eventSource) {\n        var eventBusMessage = this.createEventBusMessage(eventSource);\n\n        EventBus eventBus = this.getEventBus();\n        eventBus.fireMeSync(eventBusMessage);\n    }\n\n    /**\n     * 创建事件消息\n     *\n     * @param eventSource 事件源\n     * @return 事件消息\n     */\n    default EventBusMessage createEventBusMessage(Object eventSource) {\n        var headMetadata = this.getHeadMetadata();\n        var userId = headMetadata.getUserId();\n        var traceId = headMetadata.getTraceId();\n\n        var eventBusMessage = new EventBusMessage();\n        eventBusMessage.setEventSource(eventSource);\n        eventBusMessage.setThreadIndex(userId);\n        eventBusMessage.setTraceId(traceId);\n\n        return eventBusMessage;\n    }\n}\n\n/**\n * 帮助 FlowContext 得到线程执行器的能力\n */\ninterface SimpleExecutor extends SimpleCommon {\n    default ExecutorRegion getExecutorRegion() {\n        return this.getBarSkeleton().getExecutorRegion();\n    }\n\n    /**\n     * 玩家对应的虚拟线程执行器\n     *\n     * @return 虚拟线程执行器\n     */\n    default Executor getVirtualExecutor() {\n        return this.getVirtualThreadExecutor().executor();\n    }\n\n    /**\n     * 玩家对应的虚拟线程执行器 ThreadExecutor\n     *\n     * @return 虚拟线程执行器 ThreadExecutor\n     * @since 21.17\n     */\n    default ThreadExecutor getVirtualThreadExecutor() {\n        // 得到用户对应的虚拟线程执行器\n        var headMetadata = this.getHeadMetadata();\n        var executorIndex = ExecutorSelectKit.getExecutorIndex(headMetadata);\n\n        var executorRegion = this.getExecutorRegion();\n        return executorRegion.getUserVirtualThreadExecutor(executorIndex);\n    }\n\n    /**\n     * 玩家对应的用户线程执行器，该执行器也是消费 action 的执行器\n     *\n     * @return 用户线程执行器\n     */\n    default Executor getExecutor() {\n        return getThreadExecutor().executor();\n    }\n\n    /**\n     * 玩家对应的用户线程执行器 ThreadExecutor，该执行器也是消费 action 的执行器\n     *\n     * @return 用户线程执行器 ThreadExecutor\n     * @since 21.17\n     */\n    default ThreadExecutor getThreadExecutor() {\n        // 得到用户对应的用户线程执行器\n        var headMetadata = this.getHeadMetadata();\n        var executorIndex = ExecutorSelectKit.getExecutorIndex(headMetadata);\n\n        var executorRegion = this.getExecutorRegion();\n        return executorRegion.getUserThreadExecutor(executorIndex);\n    }\n\n    /**\n     * 使用用户线程执行任务，该方法具备全链路调用日志跟踪\n     *\n     * @param command 任务\n     */\n    default void execute(Runnable command) {\n        var headMetadata = this.getHeadMetadata();\n        var traceId = headMetadata.getTraceId();\n\n        if (Objects.isNull(traceId)) {\n            this.getExecutor().execute(command);\n            return;\n        }\n\n        this.getExecutor().execute(InternalFlowContextKit.decorator(traceId, command));\n    }\n\n    /**\n     * 使用虚拟线程执行任务，该方法具备全链路调用日志跟踪\n     *\n     * @param command 任务\n     */\n    default void executeVirtual(Runnable command) {\n        var headMetadata = this.getHeadMetadata();\n        var traceId = headMetadata.getTraceId();\n\n        if (Objects.isNull(traceId)) {\n            this.getVirtualExecutor().execute(command);\n            return;\n        }\n\n        this.getVirtualExecutor().execute(InternalFlowContextKit.decorator(traceId, command));\n    }\n}\n\n/**\n * 帮助 FlowContext 得到创建 barMessage 消息的能力\n */\ninterface SimpleBarMessageCreator extends SimpleCommon {\n    /**\n     * Create a RequestMessage object and use some of the properties of the current FlowContext HeadMetadata.\n     * see {@link HeadMetadata#cloneHeadMetadata()}\n     *\n     * @param cmdInfo cmdInfo\n     * @return request\n     */\n    default RequestMessage createRequestMessage(CmdInfo cmdInfo) {\n        return createRequestMessage(cmdInfo, null);\n    }\n\n    /**\n     * 创建一个 request 对象，并使用当前 FlowContext HeadMetadata 部分属性。\n     * <pre>\n     *     HeadMetadata 对象以下属性不会赋值，如有需要，请自行赋值\n     *       sourceClientId\n     *       endPointClientId\n     *       rpcCommandType\n     *       msgId\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @param data    业务参数\n     * @return request\n     */\n    default RequestMessage createRequestMessage(final CmdInfo cmdInfo, final Object data) {\n\n        var headMetadata = this.getHeadMetadata()\n                .cloneHeadMetadata()\n                .setCmdInfo(cmdInfo);\n\n        var requestMessage = new RequestMessage();\n        requestMessage.setHeadMetadata(headMetadata);\n\n        if (Objects.nonNull(data)) {\n            requestMessage.setData(data);\n        }\n\n        return requestMessage;\n    }\n\n    /**\n     * 创建响应对象，通常用于广播\n     * <pre>\n     *     响应对象中的 HeadMetadata 对象，会复用当前用户的一些信息；\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @param data    业务数据\n     * @return 响应对象\n     */\n    default ResponseMessage createResponseMessage(CmdInfo cmdInfo, Object data) {\n        ResponseMessage responseMessage = this.createResponseMessage(cmdInfo);\n        responseMessage.setData(data);\n        return responseMessage;\n    }\n\n    /**\n     * 创建响应对象，通常用于广播\n     * <pre>\n     *     响应对象中的 HeadMetadata 对象，会复用当前用户的一些信息；\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @return 响应对象\n     * @since 21.25\n     */\n    default ResponseMessage createResponseMessage(CmdInfo cmdInfo) {\n        /*\n         * 创建一个 HeadMetadata，并使用原有的一些信息；\n         * 在广播时，只会给 HeadMetadata 中指定的游戏对外服广播。\n         */\n        var headMetadata = this.getHeadMetadata();\n\n        var headMetadataClone = headMetadata\n                .cloneHeadMetadata()\n                .setCmdInfo(cmdInfo)\n                .setEndPointClientId(headMetadata.getEndPointClientId())\n                .setSourceClientId(headMetadata.getSourceClientId());\n\n        // 创建一个响应对象\n        var responseMessage = new ResponseMessage();\n        responseMessage.setHeadMetadata(headMetadataClone);\n\n        return responseMessage;\n    }\n}\n\ninterface SimpleCommon extends FlowOptionDynamic {\n\n    /**\n     * FlowContext request HeadMetadata\n     *\n     * @return HeadMetadata\n     */\n    HeadMetadata getHeadMetadata();\n\n    /**\n     * 业务框架\n     *\n     * @return 所关联的业务框架\n     */\n    BarSkeleton getBarSkeleton();\n\n    /**\n     * 当前请求的路由\n     *\n     * @return 路由\n     */\n    default CmdInfo getCmdInfo() {\n        return this.getHeadMetadata().getCmdInfo();\n    }\n\n    /**\n     * userId\n     *\n     * @return userId\n     */\n    default long getUserId() {\n        return this.getHeadMetadata().getUserId();\n    }\n}\n\ninterface UserIdSetting extends SimpleCommunication {\n\n    /**\n     * After binding the userId, it means the login is successful\n     *\n     * @param userId userId\n     * @return true:login success\n     * @since 21.23\n     */\n    default boolean bindingUserId(long userId) {\n        return this.bindingUserIdAndGetResult(userId).success();\n    }\n\n    /**\n     * After binding the userId, it means the login is successful\n     *\n     * @param userId userId\n     * @return result\n     * @since 21.23\n     */\n    default SettingUserIdResult bindingUserIdAndGetResult(final long userId) {\n\n        if (userId <= 0) {\n            return SettingUserIdResult.ofError(\"The userId must be greater than 0\");\n        }\n\n        var headMetadata = getHeadMetadata();\n        if (headMetadata.getUserId() != 0) {\n            String message = \"The setting of the parameter userId failed because the userId already exists. [parameterUserId:%d userId:%d]\"\n                    .formatted(userId, headMetadata.getUserId());\n\n            return SettingUserIdResult.ofError(message);\n        }\n\n        try {\n            var settingUserIdMessage = SettingUserIdMessage.of(userId, headMetadata);\n            SettingUserIdMessageResponse settingUserIdMessageResponse = this.getBrokerClientContext().invokeSync(settingUserIdMessage);\n\n            if (Objects.isNull(settingUserIdMessageResponse) || !settingUserIdMessageResponse.isSuccess()) {\n                return SettingUserIdResult.ofError(\"Login Failed\");\n            }\n        } catch (Exception e) {\n            return SettingUserIdResult.ofError(e);\n        }\n\n        headMetadata.setUserId(userId);\n        return SettingUserIdResult.SUCCESS;\n    }\n\n    /**\n     * After setting the userId, it means the login is successful\n     *\n     * @param userId userId\n     * @return true:login success\n     * @since 21.19\n     * @deprecated see {@link #bindingUserId(long)}\n     */\n    @Deprecated\n    default boolean setUserId(long userId) {\n        return this.bindingUserId(userId);\n    }\n\n    /**\n     * After setting the userId, it means the login is successful\n     *\n     * @param userId userId\n     * @return result\n     * @since 21.19\n     * @deprecated see {@link #bindingUserIdAndGetResult(long)}\n     */\n    @Deprecated\n    default SettingUserIdResult setUserIdAndGetResult(final long userId) {\n        return this.bindingUserIdAndGetResult(userId);\n    }\n}\n\n@UtilityClass\nclass InternalFlowContextKit {\n    <T> void decorate(String traceId, Consumer<T> callback, T response) {\n        try {\n            MDC.put(TraceKit.traceName, traceId);\n            callback.accept(response);\n        } finally {\n            MDC.clear();\n        }\n    }\n\n    Runnable decorator(String traceId, Runnable command) {\n        // 装饰者\n        return () -> {\n            try {\n                MDC.put(TraceKit.traceName, traceId);\n                command.run();\n            } finally {\n                MDC.clear();\n            }\n        };\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/ResponseMessageCreate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\n\n/**\n * 创建响应对象\n *\n * @author 渔民小镇\n * @date 2022-01-16\n */\npublic interface ResponseMessageCreate {\n    /**\n     * 创建 responseMessage\n     *\n     * @return responseMessage\n     */\n    ResponseMessage createResponseMessage();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/UserAttachment.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow;\n\nimport java.io.Serializable;\n\n/**\n * 元信息接口\n * <pre>\n *     注意：框架默认使用的是 protobuf 编解码，所以建议子类添加 ProtobufClass 注解\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-27\n */\npublic interface UserAttachment extends Serializable {\n\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/attr/FlowAttr.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.attr;\n\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport com.iohao.game.action.skeleton.core.commumication.ChannelContext;\nimport com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\n\n/**\n * flow 上下文的一些扩展属性\n * <pre>\n *     参考 {@link FlowContext}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-31\n */\npublic interface FlowAttr {\n    /** 异常消息 */\n    FlowOption<String> msgException = FlowOption.valueOf(\"msgException\");\n    /** 当前项目启动的服务上下文（当前服务器），see: BrokerClient */\n    FlowOption<BrokerClientContext> brokerClientContext = FlowOption.valueOf(\"brokerClientContext\");\n    FlowOption<CommunicationAggregationContext> aggregationContext = FlowOption.valueOf(\"aggregationContext\");\n    /** 通信通道接口 */\n    FlowOption<ChannelContext> channelContext = FlowOption.valueOf(\"channelContext\");\n    /** 逻辑服 id */\n    FlowOption<String> logicServerId = FlowOption.valueOf(\"logicServerId\");\n    /** 逻辑服 tag 类型 */\n    FlowOption<String> logicServerTag = FlowOption.valueOf(\"logicServerTag\");\n    /** action 中的业务参数 */\n    FlowOption<Object> actionBizParam = FlowOption.valueOf(\"actionBizParam\");\n    /** 当前线程执行器 */\n    FlowOption<ThreadExecutor> threadExecutor = FlowOption.valueOf(\"threadExecutor\");\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/attr/FlowOption.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.attr;\n\n/**\n * FlowOption\n *\n * @author 渔民小镇\n * @date 2022-03-15\n */\npublic record FlowOption<T>(String name) {\n    public static <T> FlowOption<T> valueOf(String name) {\n        return new FlowOption<>(name);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/attr/FlowOptionDynamic.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.attr;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 动态属性接口\n *\n * @author 渔民小镇\n * @date 2022-03-15\n */\npublic interface FlowOptionDynamic {\n    /**\n     * 获取动态成员属性\n     *\n     * @return 动态成员属性\n     */\n    Map<FlowOption<?>, Object> getOptions();\n\n    /**\n     * 是否有选项值。\n     *\n     * @param option option key\n     * @return true 存在\n     */\n    default boolean hasOption(FlowOption<?> option) {\n        return this.getOptions().containsKey(option);\n    }\n\n    /**\n     * 在动态属性中 获取选项值。\n     *\n     * @param option 选项值\n     * @return value\n     */\n    @SuppressWarnings(\"unchecked\")\n    default <T> T option(FlowOption<T> option) {\n        return (T) this.getOptions().get(option);\n    }\n\n    /**\n     * 在动态属性中设置值\n     *\n     * @param option option\n     * @param value  设置的值，如果是 null 用于删除前一个\n     * @param <T>    t\n     * @return 前一个值\n     */\n    @SuppressWarnings(\"unchecked\")\n    default <T> T option(FlowOption<T> option, T value) {\n\n        if (Objects.isNull(value)) {\n            return (T) this.getOptions().remove(option);\n        }\n\n        return (T) this.getOptions().put(option, value);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/attr/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 动态属性\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core.flow.attr;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DebugInOut.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.IoGameVersion;\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.core.doc.ActionCommandDoc;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.protocol.wrapper.ByteValueList;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.Setter;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.BiConsumer;\n\n\n/**\n * 业务框架插件 - <a href=\"https://iohao.github.io/game/docs/core_plugin/action_debug\">控制台输出插件</a>。\n * <p>\n * DebugInOut 是控制台输出插件，主要关注点\n * <pre>\n *     1. 快速导航到请求处理的业务代码中（执行的 action ）\n *     2. 当前发起请求的用户（玩家）\n *     3. 玩家当前所使用的连接方式（webSocket、Tcp、udp）\n *     4. 执行 action 的线程\n *     5. 执行 action 耗时情况\n *     6. 路由、类信息、方法信息等\n *     7. action 接收的请求参数\n *     8. action 响应给玩家的数据（响应结果）\n * </pre>\n * 日志输出预览\n * <pre>\n * ┏━━━━━ Debug. [(ActivityAction.java:1).hello] ━━━━━ [cmd:1-0 65536] ━━━━━ [xxx逻辑服] ━━━━━ [id:76526c134cc88232379167be83e4ddfc]\n * ┣ userId: 1\n * ┣ 参数: active : Active(id=101, name=塔姆)\n * ┣ 响应: 塔姆, I'm here\n * ┣ 时间: 1 ms (业务方法总耗时)\n * ┗━━━━━ [ioGameVersion] ━━━━━ [线程:User-8-2] ━━━━━━━━━━ [traceId:956230991452569600] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n *\n * 参数 :  通常是游戏前端传入的值\n * 响应：通常是业务方法返回的值 （游戏后端人员编写的业务）\n * 时间：执行业务方法总耗时\n *\n * [(ActivityAction.java:1).hello] 表示业务方法是在这个类运行的，其运行的业务方法名是 hello\n * cmd : 表示当前访问的路由\n * 逻辑服 : 当前游戏逻辑服与其 id\n *\n * ioGameVersion : 表示当前使用的 ioGame 版本\n * traceId : 全链路调用日志跟踪 id，每个请求唯一\n *\n * 有了以上信息，游戏开发者可以很快的定位问题。\n * 控制台会打印方法所在的类，包括方法所在的代码行数。\n * 这样可以使得开发者在开发工具中快速的导航到对应的代码；\n * 这是一个在开发阶段很有用的功能。\n *\n * 如果没有可视化的信息，开发中会浪费很多时间在前后端的沟通上，问题包括：\n * <ul>\n *     <li>是否传参问题 （游戏前端说传了）</li>\n *     <li>是否响应问题（游戏后端说返回了）</li>\n *     <li>业务执行时长问题 （游戏前端说没收到响应， 游戏后端说早就响应了）</li>\n *     <li>代码导航</li>\n * </ul>\n *\n * 其中代码导航可以让开发者快速的跳转到业务类对应代码中，在多人合作的项目中，可以快速的知道业务经过了哪些方法的执行，使得我们可以快速的进行阅读或修改；\n *\n * </pre>\n * <p>\n * see beetlsql DebugInterceptor\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\npublic final class DebugInOut implements ActionMethodInOut {\n    final long time;\n\n    /**\n     * 开发者可自定义打印\n     * <pre>\n     *     message: 可用于直接输出的 debug 信息\n     *\n     *     通过此方法，可以做一些简单的逻辑，如：\n     *     不输出某个 cmd 的信息、\n     *     或使用 log.info 来打印 message ... 等等。\n     * </pre>\n     */\n    @Setter\n    BiConsumer<String, FlowContext> printConsumer = (message, flowContext) -> {\n        // 打印 message\n        IoGameBanner.printlnMsg(message);\n    };\n\n    public DebugInOut() {\n        this(0);\n    }\n\n    /**\n     * @param time >= 这个时间才打印\n     */\n    public DebugInOut(long time) {\n        this.time = time;\n    }\n\n    @Override\n    public void fuckIn(final FlowContext flowContext) {\n        // 记录当前时间\n        flowContext.inOutStartTime();\n    }\n\n    @Override\n    public void fuckOut(final FlowContext flowContext) {\n\n        long ms = flowContext.getInOutTime();\n\n        if (this.time > ms) {\n            return;\n        }\n\n        ActionCommand actionCommand = flowContext.getActionCommand();\n        ActionCommandDoc actionCommandDoc = actionCommand.getActionCommandDoc();\n        Class<?> cc = actionCommand.getActionControllerClazz();\n\n        Map<String, Object> paramMap = new HashMap<>();\n        paramMap.put(\"ioGameVersion\", IoGameVersion.VERSION);\n        paramMap.put(\"threadName\", Thread.currentThread().getName());\n        paramMap.put(\"className\", cc.getSimpleName());\n        paramMap.put(\"actionMethodName\", actionCommand.getActionMethodName());\n        paramMap.put(\"time\", ms);\n        paramMap.put(\"lineNumber\", actionCommandDoc.getLineNumber());\n        // 路由信息\n        CmdInfo cmdInfo = flowContext.getCmdInfo();\n        paramMap.put(\"cmdInfo\", CmdKit.mergeToShort(cmdInfo.getCmdMerge()));\n        paramMap.put(\"userId\", flowContext.getUserId());\n\n        paramMap.put(\"paramName\", \"\");\n        paramMap.put(\"paramData\", \"\");\n        paramMap.put(\"returnData\", \"\");\n\n        paramMap.put(\"logicServerId\", flowContext.option(FlowAttr.logicServerId));\n        paramMap.put(\"logicServerTag\", flowContext.option(FlowAttr.logicServerTag));\n\n        extractedJoin(flowContext, paramMap);\n\n        extractedTraceId(flowContext, paramMap);\n\n        methodRequestParam(flowContext, paramMap);\n\n        ResponseMessage responseMessage = flowContext.getResponse();\n\n        extractedI18n(paramMap);\n\n        if (responseMessage.hasError()) {\n            this.printValidate(flowContext, paramMap);\n        } else {\n            this.printNormal(flowContext, paramMap);\n        }\n    }\n\n    private static void extractedI18n(Map<String, Object> paramMap) {\n        String debugInOutThreadName = Bundle.getMessage(MessageKey.debugInOutThreadName);\n        paramMap.put(MessageKey.debugInOutThreadName, debugInOutThreadName);\n\n        String debugInOutParamName = Bundle.getMessage(MessageKey.debugInOutParamName);\n        paramMap.put(MessageKey.debugInOutParamName, debugInOutParamName);\n\n        String debugInOutReturnData = Bundle.getMessage(MessageKey.debugInOutReturnData);\n        paramMap.put(MessageKey.debugInOutReturnData, debugInOutReturnData);\n\n        String debugInOutErrorCode = Bundle.getMessage(MessageKey.debugInOutErrorCode);\n        paramMap.put(MessageKey.debugInOutErrorCode, debugInOutErrorCode);\n\n        String debugInOutErrorMsg = Bundle.getMessage(MessageKey.debugInOutErrorMsg);\n        paramMap.put(MessageKey.debugInOutErrorMsg, debugInOutErrorMsg);\n\n        String debugInOutTime = Bundle.getMessage(MessageKey.debugInOutTime);\n        paramMap.put(MessageKey.debugInOutTime, debugInOutTime);\n    }\n\n    private static void extractedTraceId(FlowContext flowContext, Map<String, Object> paramMap) {\n        HeadMetadata headMetadata = flowContext.getHeadMetadata();\n        String traceId = headMetadata.getTraceId();\n\n        if (Objects.isNull(traceId)) {\n            paramMap.put(\"traceId\", \"\");\n            return;\n        }\n\n        String str = String.format(\" [traceId:%s] \", traceId);\n        paramMap.put(\"traceId\", str);\n    }\n\n    private static void extractedJoin(FlowContext flowContext, Map<String, Object> paramMap) {\n        HeadMetadata headMetadata = flowContext.getHeadMetadata();\n        int stick = headMetadata.getStick();\n\n        String connectionWay = Bundle.getMessage(MessageKey.connectionWay);\n\n        String str = switch (stick) {\n            case 1 -> \" [%s:TCP] \".formatted(connectionWay);\n            case 2 -> \" [%s:WebSocket] \".formatted(connectionWay);\n            case 3 -> \" [%s:UDP] \".formatted(connectionWay);\n            default -> \"\";\n        };\n\n        paramMap.put(\"joinName\", str);\n    }\n\n    private void printValidate(FlowContext flowContext, Map<String, Object> paramMap) {\n\n        ResponseMessage responseMessage = flowContext.getResponse();\n        paramMap.put(\"errorCode\", responseMessage.getResponseStatus());\n        paramMap.put(\"validatorMsg\", responseMessage.getValidatorMsg());\n\n        if (StrKit.isEmpty(responseMessage.getValidatorMsg())) {\n            paramMap.put(\"validatorMsg\", flowContext.option(FlowAttr.msgException));\n        }\n\n        String template = \"\"\"\n                ┏━━━━━ Error. [({className}.java:{lineNumber}).{actionMethodName}] ━━━ {cmdInfo} ━━━ [{logicServerTag}] ━━━ [id:{logicServerId}]\n                ┣ userId: {userId}\n                ┣ {debugInOutParamName}: {paramName} : {paramData}\n                ┣ {debugInOutErrorCode}: {errorCode}\n                ┣ {debugInOutErrorMsg}: {validatorMsg}\n                ┣ {debugInOutTime}: {time} ms\n                ┗━━━━━ [ioGame:{ioGameVersion}] ━━━━━ [{debugInOutThreadName}:{threadName}] ━━━━━{joinName}━━━━━{traceId}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n                \"\"\";\n\n        String message = StrKit.format(template, paramMap);\n        this.printConsumer.accept(message, flowContext);\n    }\n\n    private void printNormal(FlowContext flowContext, Map<String, Object> paramMap) {\n        methodResponseData(flowContext, paramMap);\n\n        ActionCommand actionCommand = flowContext.getActionCommand();\n\n        if (actionCommand.getActionMethodReturnInfo().isVoid()) {\n            paramMap.put(\"returnData\", \"void\");\n        }\n\n        String template = \"\"\"\n                ┏━━━━━ Debug. [({className}.java:{lineNumber}).{actionMethodName}] ━━━━━ {cmdInfo} ━━━━━ [{logicServerTag}] ━━━━━ [id:{logicServerId}]\n                ┣ userId: {userId}\n                ┣ {debugInOutParamName}: {paramName} : {paramData}\n                ┣ {debugInOutReturnData}: {returnData}\n                ┣ {debugInOutTime}: {time} ms\n                ┗━━━━━ [ioGame:{ioGameVersion}] ━━━━━ [{debugInOutThreadName}:{threadName}] ━━━━━{joinName}━━━━━{traceId}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n                \"\"\";\n\n        String message = StrKit.format(template, paramMap);\n        this.printConsumer.accept(message, flowContext);\n    }\n\n    private void methodResponseData(FlowContext flowContext, Map<String, Object> paramMap) {\n        Object data = flowContext.getMethodResult();\n\n        if (Objects.isNull(data)) {\n            data = \"null\";\n        }\n\n        // 将 ByteValueList 内的元素打印\n        if (data instanceof ByteValueList byteValueList && CollKit.notEmpty(byteValueList.values)) {\n            ActionCommand actionCommand = flowContext.getActionCommand();\n            ActionCommand.ActionMethodReturnInfo actionMethodReturnInfo = actionCommand.getActionMethodReturnInfo();\n            Class<?> actualTypeArgumentClazz = actionMethodReturnInfo.getActualTypeArgumentClazz();\n\n            data = byteValueList.values.stream()\n                    .map(bytes -> DataCodecKit.decode(bytes, actualTypeArgumentClazz))\n                    .toList();\n        }\n\n        paramMap.put(\"returnData\", data);\n    }\n\n    private void methodRequestParam(FlowContext flowContext, Map<String, Object> paramMap) {\n        ActionCommand actionCommand = flowContext.getActionCommand();\n        if (!actionCommand.isMethodHasParam()) {\n            return;\n        }\n\n        final var paramInfos = actionCommand.getParamInfos();\n\n        for (ActionCommand.ParamInfo paramInfo : paramInfos) {\n            Class<?> paramClazz = paramInfo.getParamClazz();\n\n            if (FlowContext.class.isAssignableFrom(paramClazz)) {\n                continue;\n            }\n\n            paramMap.put(\"paramName\", paramInfo.getName());\n\n            Object bizData = flowContext.option(FlowAttr.actionBizParam);\n\n            if (Objects.isNull(bizData)) {\n                bizData = \"null\";\n            }\n\n            paramMap.put(\"paramData\", bizData);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionAfter.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.commumication.ChannelContext;\nimport com.iohao.game.action.skeleton.core.flow.ActionAfter;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContextKit;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\n\n/**\n * flow - 默认的 ActionAfter\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic final class DefaultActionAfter implements ActionAfter {\n    @Override\n    public void execute(final FlowContext flowContext) {\n\n        ChannelContext channelContext = FlowContextKit.getChannelContext(flowContext);\n\n        // 有错误就响应给调用方\n        final ResponseMessage response = flowContext.getResponse();\n        if (response.hasError()) {\n            channelContext.sendResponse(response);\n            return;\n        }\n\n        // action 方法返回值是 void 的，不做处理\n        ActionCommand actionCommand = flowContext.getActionCommand();\n        if (actionCommand.getActionMethodReturnInfo().isVoid()) {\n            return;\n        }\n\n        // 将数据回传给调用方\n        channelContext.sendResponse(response);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodExceptionProcess.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodExceptionProcess;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * flow - default 异常处理\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@Slf4j\npublic final class DefaultActionMethodExceptionProcess implements ActionMethodExceptionProcess {\n    @Override\n    public MsgException processException(final Throwable e) {\n\n        if (e instanceof MsgException msgException) {\n            return msgException;\n        }\n\n        // 到这里，一般不是用户自定义的错误，很可能是开发者引入的第三方包或自身未捕获的错误等情况\n        log.error(e.getMessage(), e);\n\n        return new MsgException(ActionErrorEnum.systemOtherErrCode);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodInvoke.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodExceptionProcess;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInvoke;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\n\n/**\n * flow - default DefaultActionMethodInvoke\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic final class DefaultActionMethodInvoke implements ActionMethodInvoke {\n\n    @Override\n    public Object invoke(final FlowContext flowContext) {\n        final var actionCommand = flowContext.getActionCommand();\n        final Object controller = flowContext.getActionController();\n        final var params = flowContext.getMethodParams();\n\n        // 方法下标\n        var actionMethodIndex = actionCommand.getActionMethodIndex();\n        // 方法访问器\n        var actionMethodAccess = actionCommand.getActionMethodAccess();\n\n        try {\n            // 调用开发者在 action 类中编写的业务方法，即 action\n            return actionMethodAccess.invoke(controller, actionMethodIndex, params);\n        } catch (Throwable e) {\n            // true 业务方法有异常\n            flowContext.setError(true);\n\n            // 异常处理\n            var barSkeleton = flowContext.getBarSkeleton();\n            ActionMethodExceptionProcess exceptionProcess = barSkeleton.getActionMethodExceptionProcess();\n            // 把业务方法抛出的异常,交由异常处理类来处理\n            return exceptionProcess.processException(e);\n        }\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodParamParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.ValidatorKit;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodParamParser;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.core.flow.parser.MethodParsers;\n\nimport java.util.Objects;\n\n/**\n * flow - 业务方法参数解析器\n *\n * @author 渔民小镇\n * @date 2022-01-12\n */\npublic final class DefaultActionMethodParamParser implements ActionMethodParamParser {\n\n    @Override\n    public Object[] listParam(final FlowContext flowContext) {\n\n        var actionCommand = flowContext.getActionCommand();\n        if (!actionCommand.isMethodHasParam()) {\n            return METHOD_PARAMS;\n        }\n\n        // 方法参数信息 数组\n        var paramInfos = actionCommand.getParamInfos();\n        var params = new Object[paramInfos.length];\n\n        for (int i = 0; i < paramInfos.length; i++) {\n            // 方法参数信息\n            ActionCommand.ParamInfo paramInfo = paramInfos[i];\n            // flow 上下文\n            if (paramInfo.isFlowContext()) {\n                params[i] = flowContext;\n                continue;\n            }\n\n            // 业务参数\n            var data = flowContext.getRequest().getData();\n            // 得到方法参数解析器，把字节解析成 action 业务参数\n            var paramClazz = paramInfo.getActualTypeArgumentClazz();\n            var methodParser = MethodParsers.getMethodParser(paramClazz);\n            var param = methodParser.parseParam(data, paramInfo);\n            params[i] = param;\n\n            flowContext.option(FlowAttr.actionBizParam, param);\n\n            // 如果开启了验证\n            if (paramInfo.isValidator()) {\n                extractedValidator(flowContext, paramInfo, param);\n            }\n        }\n\n        return params;\n    }\n\n    private void extractedValidator(FlowContext flowContext, ActionCommand.ParamInfo paramInfo, Object param) {\n        // 获取分组信息\n        Class<?>[] groups = paramInfo.getValidatorGroups();\n        // 进行 JSR380 相关的验证\n        String validateMsg = ValidatorKit.validate(param, groups);\n        // 有错误消息，表示验证不通过\n        if (Objects.nonNull(validateMsg)) {\n            var response = flowContext.getResponse();\n            response.setValidatorMsg(validateMsg);\n            response.setResponseStatus(ActionErrorEnum.validateErrCode.getCode());\n        }\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodResultWrap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodResultWrap;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.core.flow.parser.MethodParser;\nimport com.iohao.game.action.skeleton.core.flow.parser.MethodParsers;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\n\n/**\n * flow - 结果包装器\n *\n * @author 渔民小镇\n * @date 2022-01-12\n */\npublic final class DefaultActionMethodResultWrap implements ActionMethodResultWrap {\n\n    @Override\n    public void wrap(final FlowContext flowContext) {\n        final ResponseMessage responseMessage = flowContext.getResponse();\n        // 业务方法的返回值\n        final Object result = flowContext.getMethodResult();\n\n        // 如果有异常错误，异常处理\n        if (flowContext.isError()) {\n\n            MsgException msgException = (MsgException) result;\n            int code = msgException.getMsgCode();\n            responseMessage.setResponseStatus(code);\n\n            flowContext.option(FlowAttr.msgException, msgException.getMessage());\n\n            return;\n        }\n\n        ActionCommand.ActionMethodReturnInfo actionMethodReturnInfo = flowContext.getActionCommand().getActionMethodReturnInfo();\n\n        /*\n         * action 方法返回值是 void 类型的，但是在广播时又复用了这个 action 为 void 的路由\n         * 所以这里的逻辑最后以 result 是否为 null 来决定是否继续做一下步的处理\n         *\n         * action 返回值是 void && 结果为 null ，就不做处理\n         * 如果 result 不为 null，就是来自广播的结果\n         */\n        if (actionMethodReturnInfo.isVoid() && result == null) {\n            return;\n        }\n\n        // 得到 action 返回值的解析器，将解析后的结果保存到 flowContext 中\n        MethodParser paramParser = MethodParsers.getMethodParser(actionMethodReturnInfo);\n        Object methodResult = paramParser.parseResult(actionMethodReturnInfo, result);\n        flowContext.setMethodResult(methodResult);\n\n        // 将 action （业务方法返回值），保存到响应对象中\n        responseMessage.setData(methodResult);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultResponseMessageCreate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.core.flow.ResponseMessageCreate;\n\n/**\n * flow - 创建响应对象\n *\n * @author 渔民小镇\n * @date 2022-01-16\n */\npublic final class DefaultResponseMessageCreate implements ResponseMessageCreate {\n    @Override\n    public ResponseMessage createResponseMessage() {\n        return new ResponseMessage();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/StatActionInOut.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.LongAdder;\nimport java.util.function.BiConsumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * 业务框架插件 - <a href=\"https://iohao.github.io/game/docs/core_plugin/action_stat\">action 调用统计插件</a>\n * <pre>\n *     StatActionInOut 是 action 调用统计插件，可以用来统计各 action 调用时的相关数据，\n *     如 action 的执行次数、总耗时、平均耗时、最大耗时、触发异常次数...等相关数据\n *     开发者可以通过这些数据来分析出项目中的热点方法、耗时方法，从而做到精准优化。\n *\n *     // StatAction 统计记录打印预览\n *     \"StatAction{cmd[1 - 0], 执行[1]次, 总耗时[8], 平均耗时[8], 最大耗时[8], 异常[0]次}\"\n * </pre>\n * <p>\n * for example\n * <pre>{@code\n *         BarSkeletonBuilder builder = ...;\n *         // action 调用统计插件，将插件添加到业务框架中\n *         var statActionInOut = new StatActionInOut();\n *         builder.addInOut(statActionInOut);\n *\n *         // 设置 StatAction 统计记录更新后的监听处理\n *         statActionInOut.setListener((statAction, time, flowContext) -> {\n *             // 简单打印统计记录值 StatAction\n *             System.out.println(statAction);\n *         });\n *\n *         // 统计域（统计值的管理器）\n *         StatActionInOut.StatActionRegion region = statActionInOut.getRegion();\n *\n *         // 遍历所有的统计数据\n *         region.forEach((cmdInfo, statAction) -> {\n *             // 简单打印统计记录值 StatAction\n *             System.out.println(statAction);\n *             // 开发者可以定时的将这些数据保存到日志或 DB 中，用于后续的分析\n *         });\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-11-17\n * @see StatAction\n * @see StatActionRegion\n * @see StatActionChangeListener\n */\npublic final class StatActionInOut implements ActionMethodInOut {\n    /** 统计域（管理 StatAction ） */\n    @Getter\n    final StatActionRegion region = new StatActionRegion();\n    /** 统计值更新监听 */\n    @Setter\n    StatActionChangeListener listener;\n\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n        // 记录当前时间\n        flowContext.inOutStartTime();\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        long time = flowContext.getInOutTime();\n\n        // StatAction 与 action 是对应关系， 1:1\n        this.region.update(time, flowContext);\n    }\n\n    /** action 调用统计插件 - 统计域，管理所有 StatAction 统计记录 */\n    public final class StatActionRegion {\n        final Map<CmdInfo, StatAction> map = new NonBlockingHashMap<>();\n\n        void update(long time, FlowContext flowContext) {\n            CmdInfo cmdInfo = flowContext.getCmdInfo();\n            StatAction statAction = getStatAction(cmdInfo);\n            statAction.update(flowContext, time);\n\n            // 统计值更新后所执行的回调方法\n            if (Objects.nonNull(StatActionInOut.this.listener)) {\n                StatActionInOut.this.listener.flow(statAction, time, flowContext);\n            }\n        }\n\n        public StatAction getStatAction(CmdInfo cmdInfo) {\n            StatAction statAction = this.map.get(cmdInfo);\n\n            // 无锁化\n            if (Objects.isNull(statAction)) {\n                var newValue = new StatAction(cmdInfo);\n                return MoreKit.putIfAbsent(this.map, cmdInfo, newValue);\n            }\n\n            return statAction;\n        }\n\n        public void forEach(BiConsumer<CmdInfo, StatAction> action) {\n            this.map.forEach(action);\n        }\n\n        public Stream<StatAction> stream() {\n            return this.map.values().stream();\n        }\n\n        @Override\n        public String toString() {\n            return map.values().stream()\n                    .map(StatAction::toString)\n                    .collect(Collectors.joining(\"\\n\"));\n        }\n    }\n\n    /** action 调用统计插件 - action 统计记录，与 action 是对应关系 1:1 */\n    @Getter\n    public final class StatAction {\n        static final List<TimeRange> emptyRangeList = List.of(TimeRange.create(Long.MAX_VALUE - 1, Long.MAX_VALUE, \"\"));\n        /** 时间范围 */\n        final List<TimeRange> timeRangeList;\n        @Getter(AccessLevel.PRIVATE)\n        final TimeRange lastTimeRange;\n\n        final CmdInfo cmdInfo;\n        /** action 执行次数统计 */\n        final LongAdder executeCount = new LongAdder();\n        /** 总耗时 */\n        final LongAdder totalTime = new LongAdder();\n        /** action 异常触发次数 */\n        final LongAdder errorCount = new LongAdder();\n        /** 最大耗时 */\n        volatile long maxTime;\n\n        private StatAction(CmdInfo cmdInfo) {\n\n            this.timeRangeList = Objects.isNull(StatActionInOut.this.listener)\n                    ? emptyRangeList\n                    : StatActionInOut.this.listener.createTimeRangeList();\n\n            if (CollKit.isEmpty(this.timeRangeList)) {\n                ThrowKit.ofIllegalArgumentException(\"this.timeRangeList is empty\");\n            }\n\n            this.cmdInfo = cmdInfo;\n            this.lastTimeRange = this.timeRangeList.getLast();\n        }\n\n        private void update(FlowContext flowContext, long time) {\n            // 调用次数 +1\n            this.executeCount.increment();\n\n            if (flowContext.isError()) {\n                this.errorCount.increment();\n            }\n\n            if (time == 0) {\n                return;\n            }\n\n            // 总耗时增加\n            this.totalTime.add(time);\n\n            // 记录最大耗时\n            if (time > maxTime) {\n                this.maxTime = time;\n            }\n        }\n\n        /**\n         * 根据消耗时间获取对应的时间范围对象；\n         * <pre>\n         *     如果没有找到对应的时间范围，取配置 List 中的最后一个元素。\n         * </pre>\n         *\n         * @param time 消耗时间\n         * @return 时间范围\n         */\n        public TimeRange getTimeRange(long time) {\n            return this.timeRangeList.stream()\n                    .filter(timeRange -> timeRange.inRange(time))\n                    .findFirst()\n                    .orElse(this.lastTimeRange);\n        }\n\n        /**\n         * 平均耗时\n         *\n         * @return 平均耗时\n         */\n        public long getAvgTime() {\n            return this.totalTime.sum() / this.executeCount.sum();\n        }\n\n        /** %s, 执行[%s]次, 异常[%s]次, 平均耗时[%d], 最大耗时[%s], 总耗时[%s] %s */\n        private final String statActionInOutToString = Bundle.getMessage(MessageKey.statActionInOutStatAction);\n\n        @Override\n        public String toString() {\n            String rangeStr = \"\";\n            if (Objects.nonNull(StatActionInOut.this.listener)) {\n                var builder = new StringBuilder();\n                for (TimeRange timeRange : this.timeRangeList) {\n                    if (timeRange.count.sum() == 0) {\n                        continue;\n                    }\n\n                    builder.append(\"\\n\\t\").append(timeRange);\n                }\n\n                rangeStr = builder.toString();\n            }\n\n            return String.format(statActionInOutToString\n                    , CmdKit.toString(this.cmdInfo.getCmdMerge())\n                    , this.executeCount\n                    , this.errorCount\n                    , this.getAvgTime()\n                    , this.maxTime\n                    , this.totalTime\n                    , rangeStr\n            );\n        }\n    }\n\n    /**\n     * action 调用统计插件 - StatAction 更新监听\n     */\n    public interface StatActionChangeListener {\n        /**\n         * StatAction 统计记录更新后调用\n         *\n         * @param statAction  action 统计记录\n         * @param time        action 执行耗时\n         * @param flowContext flowContext\n         */\n        void changed(StatAction statAction, long time, FlowContext flowContext);\n\n        /**\n         * 创建时间范围；如果想将统计分得更细，只需要创建更多的时间范围。\n         * <p>\n         * 参考示例\n         * <pre>{@code\n         *      List.of(\n         *          TimeRange.create(500, 1000),\n         *          TimeRange.create(1000, 1500),\n         *          TimeRange.create(1500, 2000),\n         *          TimeRange.create(2000, Long.MAX_VALUE, \"> 2000\"))\n         * }\n         * </pre>\n         *\n         * @return 时间范围，List 必须非空\n         */\n        default List<TimeRange> createTimeRangeList() {\n            return List.of(\n                    TimeRange.create(500, 1000),\n                    TimeRange.create(1000, 1500),\n                    TimeRange.create(1500, 2000),\n                    TimeRange.create(2000, Long.MAX_VALUE, \"> 2000\"));\n        }\n\n        /**\n         * 触发条件，触发 updateTimeRange 方法的前置条件\n         * <pre>\n         *     开发者可以通常此方法来决定是否触发 updateTimeRange 方法。\n         *\n         *     比如可以在该方法内判断，只对某个或某些玩家来做监控。\n         * </pre>\n         *\n         * @param statAction  action 统计记录\n         * @param time        action 执行耗时\n         * @param flowContext flowContext\n         * @return true 表示满足条件；当为 true 时，会调用 updateTimeRange 方法\n         */\n        default boolean triggerUpdateTimeRange(StatAction statAction, long time, FlowContext flowContext) {\n            return false;\n        }\n\n        /**\n         * StatAction 统计记录更新中调用，当 trigger 方法为 true 时会调用\n         *\n         * @param statAction  action 统计记录\n         * @param time        action 执行耗时\n         * @param flowContext flowContext\n         */\n        default void updateTimeRange(StatAction statAction, long time, FlowContext flowContext) {\n            statAction.getTimeRange(time).increment();\n        }\n\n        /**\n         * StatAction 更新监听流程\n         * <pre>\n         *     默认实现的流程为\n         *     1 先判断 triggerUpdateTimeRange 是否满足条件\n         *     2 当 triggerUpdateTimeRange 为 true 时，会执行 updateTimeRange\n         *     3 无论如何 changed 总是会被执行的\n         * </pre>\n         *\n         * @param statAction  action 统计记录\n         * @param time        action 执行耗时\n         * @param flowContext flowContext\n         */\n        default void flow(StatAction statAction, long time, FlowContext flowContext) {\n            if (this.triggerUpdateTimeRange(statAction, time, flowContext)) {\n                this.updateTimeRange(statAction, time, flowContext);\n            }\n\n            this.changed(statAction, time, flowContext);\n        }\n    }\n\n    /**\n     * action 调用统计插件 - 时间范围记录\n     *\n     * @param start 开始时间，包含该时间\n     * @param end   结束时间，包含该时间\n     * @param count 该时间范围所触发的执行次数\n     * @param name  name\n     */\n    public record TimeRange(long start, long end, LongAdder count, String name) {\n        /**\n         * 创建时间范围\n         *\n         * @param start 开始时间，包含该时间\n         * @param end   结束时间，包含该时间\n         * @return TimeRange\n         */\n        public static TimeRange create(long start, long end) {\n            return create(start, end, start + \" ~ \" + end);\n        }\n\n        /**\n         * 创建时间范围，并指定名称\n         *\n         * @param start 开始时间，包含该时间\n         * @param end   结束时间，包含该时间\n         * @param name  打印时的名称\n         * @return TimeRange\n         */\n        public static TimeRange create(long start, long end, String name) {\n            return new TimeRange(start, end, new LongAdder(), name);\n        }\n\n        boolean inRange(long time) {\n            return time >= this.start && time <= this.end;\n        }\n\n        /**\n         * 统计 + 1\n         */\n        void increment() {\n            this.count.increment();\n        }\n\n        /** %s ms 的请求共 [%d] 个 */\n        private static final String statActionInOutTimeRange = Bundle.getMessage(MessageKey.statActionInOutTimeRange);\n\n        @Override\n        public String toString() {\n            return String.format(statActionInOutTimeRange, this.name, this.count.sum());\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/ThreadMonitorInOut.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\nimport lombok.Getter;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.TreeMap;\nimport java.util.concurrent.atomic.LongAdder;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\n/**\n * 业务框架插件 - <a href=\"https://iohao.github.io/game/docs/core_plugin/action_thread_monitor\">业务线程监控插件</a>\n * <p>\n * for example\n * <pre>{@code\n * BarSkeletonBuilder builder = ...;\n * // 业务线程监控插件，将插件添加到业务框架中\n * var threadMonitorInOut = new ThreadMonitorInOut();\n * builder.addInOut(threadMonitorInOut);\n * }</pre>\n * <p>\n * 打印预览\n * <pre>\n *     业务线程[RequestMessage-8-1] 共执行了 1 次业务，平均耗时 1 ms, 剩余 91 个任务未执行\n *     业务线程[RequestMessage-8-2] 共执行了 1 次业务，平均耗时 1 ms, 剩余 0 个任务未执行\n *     业务线程[RequestMessage-8-3] 共执行了 1 次业务，平均耗时 1 ms, 剩余 36 个任务未执行\n *     业务线程[RequestMessage-8-4] 共执行了 1 次业务，平均耗时 1 ms, 剩余 0 个任务未执行\n *     业务线程[RequestMessage-8-5] 共执行了 1 次业务，平均耗时 1 ms, 剩余 88 个任务未执行\n *     业务线程[RequestMessage-8-6] 共执行了 1 次业务，平均耗时 1 ms, 剩余 0 个任务未执行\n *     业务线程[RequestMessage-8-7] 共执行了 7 次业务，平均耗时 1 ms, 剩余 56 个任务未执行\n *     业务线程[RequestMessage-8-8] 共执行了 1 次业务，平均耗时 1 ms, 剩余 0 个任务未执行\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-11-22\n */\n@Getter\npublic final class ThreadMonitorInOut implements ActionMethodInOut {\n    final ThreadMonitorRegion region = new ThreadMonitorRegion();\n\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n        flowContext.inOutStartTime();\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        ThreadExecutor threadExecutor = flowContext.option(FlowAttr.threadExecutor);\n        if (Objects.nonNull(threadExecutor)) {\n            region.update(flowContext.getInOutTime(), threadExecutor);\n        }\n    }\n\n    /**\n     * 业务线程监控插件 - 线程监控相关信息\n     */\n    @Getter\n    public static class ThreadMonitorRegion {\n        final Map<String, ThreadMonitor> map = new NonBlockingHashMap<>();\n\n        private ThreadMonitor getStatThread(ThreadExecutor threadExecutor) {\n            String name = threadExecutor.name();\n            ThreadMonitor threadMonitor = this.map.get(name);\n\n            // 无锁化\n            if (Objects.isNull(threadMonitor)) {\n                ThreadMonitor newValue = ThreadMonitor.create(name, threadExecutor);\n                return MoreKit.putIfAbsent(this.map, name, newValue);\n            }\n\n            return threadMonitor;\n        }\n\n        void update(long time, ThreadExecutor threadExecutor) {\n            this.getStatThread(threadExecutor).increment(time);\n        }\n\n        public void forEach(Consumer<ThreadMonitor> action) {\n            this.map.values()\n                    .stream()\n                    .filter(ThreadMonitor::notEmpty)\n                    .forEach(action);\n        }\n\n        @Override\n        public String toString() {\n            Map<String, ThreadMonitor> sortMap = new TreeMap<>(this.map);\n            return sortMap.values().stream()\n                    .filter(ThreadMonitor::notEmpty)\n                    .map(ThreadMonitor::toString)\n                    .collect(Collectors.joining(\"\\n\"));\n        }\n    }\n\n    /**\n     * 线程监控相关信息\n     *\n     * @param name         业务线程名\n     * @param executeCount 线程已执行任务的次数\n     * @param totalTime    执行任务的总耗时\n     * @param executor     ThreadPoolExecutor\n     */\n    public record ThreadMonitor(String name, LongAdder executeCount, LongAdder totalTime, ThreadExecutor executor) {\n\n        public static ThreadMonitor create(String name, ThreadExecutor executor) {\n            return new ThreadMonitor(name, new LongAdder(), new LongAdder(), executor);\n        }\n\n        void increment(long time) {\n            this.executeCount.increment();\n            this.totalTime.add(time);\n        }\n\n        boolean notEmpty() {\n            return this.executeCount.sum() > 0;\n        }\n\n        /**\n         * 平均耗时\n         *\n         * @return 平均耗时\n         */\n        public long getAvgTime() {\n            return this.totalTime.sum() / this.executeCount.sum();\n        }\n\n        /**\n         * 当前剩余还没有执行的任务数\n         *\n         * @return 剩余还没有执行的任务数\n         */\n        public int countRemaining() {\n            return Optional.ofNullable(this.executor)\n                    .map(ThreadExecutor::getWorkQueue)\n                    .orElse(0);\n        }\n\n        /** 业务线程[%s] 共执行了 %s 次业务，平均耗时 %d ms, 剩余 %d 个任务未执行 */\n        private static final String threadMonitorInOutThreadMonitor = Bundle.getMessage(MessageKey.threadMonitorInOutThreadMonitor);\n\n        @Override\n        public String toString() {\n            return String.format(threadMonitorInOutThreadMonitor\n                    , this.name\n                    , this.executeCount.sum()\n                    , this.getAvgTime()\n                    , this.countRemaining()\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/TimeRangeInOut.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.common.kit.time.CacheTimeKit;\nimport com.iohao.game.common.kit.time.FormatTimeKit;\nimport lombok.Getter;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.time.LocalDate;\nimport java.time.LocalTime;\nimport java.util.*;\nimport java.util.concurrent.atomic.LongAdder;\nimport java.util.function.BiConsumer;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\n/**\n * 业务框架插件 - <a href=\"https://iohao.github.io/game/docs/core_plugin/action_time_range\">各时间段调用统计插件</a>\n *\n * <pre>{@code\n *     BarSkeletonBuilder builder = ...;\n *     // 各时间段 action 调用统计插件，将插件添加到业务框架中\n *     var timeRangeInOut = new TimeRangeInOut();\n *     builder.addInOut(timeRangeInOut);\n * }\n * </pre>\n * 打印预览 - 一天各小时各分钟阶段的调用统计数据\n * <pre>\n *  2023-11-29 action 调用次数 共 [100] 次\n * \t0:00 共 8 次; - [15~30分钟 3 次] - [30~45分钟 2 次] - [45~59分钟 3 次]\n * \t1:00 共 9 次; - [0~15分钟 1 次] - [15~30分钟 4 次] - [30~45分钟 1 次] - [45~59分钟 3 次]\n * \t2:00 共 4 次; - [0~15分钟 1 次] - [15~30分钟 2 次] - [45~59分钟 1 次]\n * \t3:00 共 2 次; - [0~15分钟 1 次] - [15~30分钟 1 次]\n * \t4:00 共 1 次; - [0~15分钟 1 次]\n * \t5:00 共 4 次; - [0~15分钟 1 次] - [15~30分钟 1 次] - [30~45分钟 1 次] - [45~59分钟 1 次]\n * \t6:00 共 5 次; - [0~15分钟 1 次] - [15~30分钟 1 次] - [30~45分钟 1 次] - [45~59分钟 2 次]\n * \t7:00 共 4 次; - [15~30分钟 2 次] - [30~45分钟 1 次] - [45~59分钟 1 次]\n * \t8:00 共 4 次; - [0~15分钟 1 次] - [30~45分钟 3 次]\n * \t9:00 共 4 次; - [15~30分钟 2 次] - [30~45分钟 2 次]\n * \t10:00 共 5 次; - [15~30分钟 2 次] - [30~45分钟 1 次] - [45~59分钟 2 次]\n * \t11:00 共 3 次; - [15~30分钟 2 次] - [45~59分钟 1 次]\n * \t12:00 共 4 次; - [0~15分钟 2 次] - [30~45分钟 2 次]\n * \t13:00 共 1 次; - [30~45分钟 1 次]\n * \t14:00 共 5 次; - [0~15分钟 1 次] - [45~59分钟 4 次]\n * \t15:00 共 6 次; - [0~15分钟 1 次] - [15~30分钟 2 次] - [45~59分钟 3 次]\n * \t16:00 共 4 次; - [0~15分钟 1 次] - [15~30分钟 1 次] - [30~45分钟 1 次] - [45~59分钟 1 次]\n * \t17:00 共 7 次; - [0~15分钟 1 次] - [15~30分钟 3 次] - [30~45分钟 3 次]\n * \t18:00 共 2 次; - [0~15分钟 1 次] - [15~30分钟 1 次]\n * \t19:00 共 7 次; - [0~15分钟 1 次] - [15~30分钟 3 次] - [30~45分钟 3 次]\n * \t20:00 共 5 次; - [15~30分钟 3 次] - [30~45分钟 2 次]\n * \t21:00 共 3 次; - [15~30分钟 2 次] - [30~45分钟 1 次]\n * \t22:00 共 1 次; - [45~59分钟 1 次]\n * \t23:00 共 2 次; - [15~30分钟 1 次] - [45~59分钟 1 次]\n * </pre>\n * set Listener example\n * <pre>{@code\n * private void setListener(TimeRangeInOut inOut) {\n *     inOut.setListener(new TimeRangeInOut.ChangeListener() {\n *         @Override\n *         public List<TimeRangeInOut.TimeRangeMinute> createListenerTimeRangeMinuteList() {\n *             return List.of(\n *                     // 只统计 0、1、59 分钟这 3 个时间点\n *                     TimeRangeInOut.TimeRangeMinute.create(0, 0),\n *                     TimeRangeInOut.TimeRangeMinute.create(1, 1),\n *                     TimeRangeInOut.TimeRangeMinute.create(59, 59)\n *             );\n *         }\n *     });\n * }\n *\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2023-11-29\n * @see ChangeListener\n */\n@Getter\npublic final class TimeRangeInOut implements ActionMethodInOut {\n\n    final TimeRangeDayRegion region = new TimeRangeDayRegion();\n    ChangeListener listener = new ChangeListener() {\n    };\n\n    /**\n     * 设置监听器\n     *\n     * @param listener 监听器\n     */\n    public void setListener(ChangeListener listener) {\n        this.listener = Objects.requireNonNull(listener);\n    }\n\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        LocalDate localDate = listener.nowLocalDate();\n        LocalTime localTime = listener.nowLocalTime();\n\n        this.region.update(localDate, localTime, flowContext);\n    }\n\n    /**\n     * 各时间段调用统计插件 - 调用统计对象域\n     */\n    @Getter\n    public final class TimeRangeDayRegion {\n        final Map<LocalDate, TimeRangeDay> map = new NonBlockingHashMap<>();\n\n        public void forEach(BiConsumer<LocalDate, TimeRangeDay> action) {\n            this.map.forEach(action);\n        }\n\n        void update(LocalDate localDate, LocalTime localTime, FlowContext flowContext) {\n\n            TimeRangeDay timeRangeDay = this.getTimeRangeDay(localDate);\n            timeRangeDay.increment(localTime);\n\n            // 变更回调\n            listener.changed(timeRangeDay, localTime, flowContext);\n        }\n\n        public TimeRangeDay getTimeRangeDay(LocalDate localDate) {\n\n            TimeRangeDay timeRangeDay = this.map.get(localDate);\n\n            // 无锁化\n            if (Objects.isNull(timeRangeDay)) {\n\n                TimeRangeDay rangeDay = TimeRangeInOut.this.listener.createTimeRangeDay(localDate);\n                timeRangeDay = this.map.putIfAbsent(localDate, Objects.requireNonNull(rangeDay));\n\n                if (Objects.isNull(timeRangeDay)) {\n                    timeRangeDay = this.map.get(localDate);\n\n                    /*\n                     * 回调昨天的数据\n                     * 原计划是想保留 map 中的数据让开发者自行处理，\n                     * 考虑到开发者可能会忘记移除 map 中的数据，为防止造成隐患，这里就直接移除昨天的数据了。\n                     */\n                    TaskKit.execute(() -> {\n                        LocalDate yesterdayLocalDate = localDate.minusDays(1);\n                        Optional.ofNullable(this.map.remove(yesterdayLocalDate))\n                                .ifPresent(timeRangeYesterday -> TimeRangeInOut.this.listener.callbackYesterday(timeRangeYesterday));\n                    });\n                }\n            }\n\n            return timeRangeDay;\n        }\n    }\n\n    /**\n     * 各时间段调用统计插件 - 一天的调用统计对象\n     *\n     * @param localDate      日期\n     * @param count          一天的 action 调用总次数\n     * @param timeRangeHours 时间段\n     */\n    public record TimeRangeDay(LocalDate localDate, LongAdder count, TimeRangeHour[] timeRangeHours) {\n        public static TimeRangeDay create(LocalDate localDate, List<TimeRangeHour> timeRangeHours) {\n\n            TimeRangeDay timeRangeDay = new TimeRangeDay(localDate, new LongAdder(), new TimeRangeHour[24]);\n\n            for (TimeRangeHour timeRangeHour : timeRangeHours) {\n                int hour = timeRangeHour.getHour();\n                timeRangeDay.timeRangeHours[hour] = timeRangeHour;\n            }\n\n            return timeRangeDay;\n        }\n\n        public Stream<TimeRangeHour> stream() {\n            return Arrays.stream(this.timeRangeHours)\n                    .filter(Objects::nonNull);\n        }\n\n        public TimeRangeHour getTimeRangeHour(LocalTime localTime) {\n            var hour = localTime.getHour();\n            return this.timeRangeHours[hour];\n        }\n\n        public void increment(LocalTime localTime) {\n\n            this.count.increment();\n\n            var timeRangeHour = this.getTimeRangeHour(localTime);\n\n            if (Objects.nonNull(timeRangeHour)) {\n                timeRangeHour.increment(localTime);\n            }\n        }\n\n        /** action 调用次数 共 [%d] 次 */\n        private static final String dayTitle = Bundle.getMessage(MessageKey.timeRangeInOutDayTitle);\n\n        @Override\n        public String toString() {\n\n            String localDateFormat = FormatTimeKit.ofPattern(\"yyyy-MM-dd\").format(this.localDate);\n\n            List<TimeRangeHour> timeRangeHoursList = stream()\n                    .filter(timeRangeHour -> timeRangeHour.count.sum() > 0)\n                    .toList();\n\n            if (CollKit.isEmpty(timeRangeHoursList)) {\n                // TimeRange，action 暂无数据\n                return localDateFormat + \" action no data\";\n            }\n\n            StringBuilder builder = new StringBuilder(localDateFormat);\n            builder.append(\" \").append(dayTitle.formatted(this.count.sum()));\n\n            for (TimeRangeHour timeRangeHour : timeRangeHoursList) {\n                builder.append(\"\\n\\t\").append(timeRangeHour);\n            }\n\n            return builder.toString();\n        }\n    }\n\n    /**\n     * 各时间段调用统计插件 - 一小时的调用统计对象\n     *\n     * @param hourTime   小时\n     * @param count      一小时的 action 调用次数\n     * @param minuteList 分钟时间段\n     */\n    public record TimeRangeHour(LocalTime hourTime, LongAdder count, List<TimeRangeMinute> minuteList) {\n\n        public static TimeRangeHour create(int hour, List<TimeRangeMinute> minuteList) {\n            var hourTime = LocalTime.of(hour, 0);\n            return new TimeRangeHour(hourTime, new LongAdder(), minuteList);\n        }\n\n        void increment(LocalTime localTime) {\n            this.count.increment();\n\n            if (CollKit.isEmpty(this.minuteList)) {\n                return;\n            }\n\n            int minute = localTime.getMinute();\n\n            this.minuteList.stream()\n                    .filter(timeRangeMinute -> timeRangeMinute.inRange(minute))\n                    .findAny()\n                    .ifPresent(TimeRangeMinute::increment);\n        }\n\n        public int getHour() {\n            return this.hourTime.getHour();\n        }\n\n        /** %d:00 共 %s 次; */\n        private static final String hourTitle = Bundle.getMessage(MessageKey.timeRangeInOutHourTitle);\n\n        @Override\n        public String toString() {\n            String hourStr = hourTitle.formatted(this.getHour(), this.count.sum());\n\n            if (CollKit.isEmpty(this.minuteList)) {\n                return hourStr;\n            }\n\n            StringBuilder builder = new StringBuilder();\n            builder.append(hourStr);\n\n            this.minuteList.stream()\n                    .filter(timeRangeMinute -> timeRangeMinute.count.sum() > 0)\n                    .forEach(timeRangeMinute -> builder.append(\" - \").append(timeRangeMinute));\n\n            return builder.toString();\n        }\n    }\n\n    /**\n     * 各时间段调用统计插件 - 分钟范围记录\n     *\n     * @param start 开始时间（分钟），包含该时间\n     * @param end   结束时间（分钟），包含该时间\n     * @param count 该时间范围所触发的执行次数\n     */\n    public record TimeRangeMinute(int start, int end, LongAdder count) {\n        /**\n         * 创建分钟范围记录\n         *\n         * @param start 开始时间（分钟），包含该时间\n         * @param end   结束时间（分钟），包含该时间\n         * @return 分钟范围记录\n         */\n        public static TimeRangeMinute create(int start, int end) {\n            return new TimeRangeMinute(start, end, new LongAdder());\n        }\n\n        boolean inRange(int minute) {\n            return minute >= this.start && minute <= this.end;\n        }\n\n        void increment() {\n            this.count.increment();\n        }\n\n        /** [%d~%d分钟，%d 次] */\n        private static final String minuteTitle = Bundle.getMessage(MessageKey.timeRangeInOutMinuteTitle);\n\n        @Override\n        public String toString() {\n            return minuteTitle.formatted(this.start, this.end, this.count.sum());\n        }\n    }\n\n    /**\n     * 各时间段调用统计插件 - 监听器\n     */\n    public interface ChangeListener {\n\n        default void changed(TimeRangeDay timeRangeDay, LocalTime localTime, FlowContext flowContext) {\n        }\n\n        /**\n         * 插件会在每天的 0:00 触发 callbackYesterday 方法，并将昨日的 TimeRangeDay 对象传入方法中\n         *\n         * @param timeRangeYesterday 一天的调用统计对象（一定不为 null）\n         */\n        default void callbackYesterday(TimeRangeDay timeRangeYesterday) {\n        }\n\n        /**\n         * LocalDate now\n         *\n         * @return LocalDate\n         */\n        default LocalDate nowLocalDate() {\n            return CacheTimeKit.nowLocalDate();\n        }\n\n        /**\n         * LocalTime now\n         *\n         * @return LocalTime\n         */\n        default LocalTime nowLocalTime() {\n            return CacheTimeKit.nowLocalTime();\n        }\n\n        /**\n         * 创建 TimeRangeDay （一天的调用统计对象）\n         *\n         * @param localDate 日期\n         * @return TimeRangeDay\n         */\n        default TimeRangeDay createTimeRangeDay(LocalDate localDate) {\n            List<TimeRangeHour> timeRangeHourList = this.createListenerTimeRangeHourList();\n            return TimeRangeDay.create(localDate, timeRangeHourList);\n        }\n\n        /**\n         * create TimeRangeHour list，需要统计的小时范围列表\n         *\n         * @return list TimeRangeHour 一小时的调用统计对象\n         */\n        default List<TimeRangeHour> createListenerTimeRangeHourList() {\n            // 创建对应 24 个小时的数据\n            return IntStream.range(0, 24)\n                    .mapToObj(this::createListenerTimeRangeHour)\n                    .toList();\n        }\n\n        /**\n         * create TimeRangeHour ，需要统计的小时范围\n         *\n         * @param hour 小时\n         * @return 一小时的调用统计对象\n         */\n        default TimeRangeHour createListenerTimeRangeHour(int hour) {\n            List<TimeRangeMinute> timeRangeMinuteList = this.createListenerTimeRangeMinuteList();\n            return TimeRangeHour.create(hour, timeRangeMinuteList);\n        }\n\n        /**\n         * create TimeRangeMinute list，分钟范围记录列表\n         *\n         * @return list 分钟范围记录\n         */\n        default List<TimeRangeMinute> createListenerTimeRangeMinuteList() {\n            return Collections.emptyList();\n        }\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/TraceIdInOut.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.common.kit.trace.TraceKit;\nimport org.slf4j.MDC;\n\nimport java.util.Objects;\n\n/**\n * 业务框架插件 - <a href=\"https://iohao.github.io/game/docs/core_plugin/action_trace\">全链路调用日志跟踪插件</a>\n *\n * @author 渔民小镇\n * @date 2023-12-20\n */\npublic final class TraceIdInOut implements ActionMethodInOut {\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n\n        HeadMetadata headMetadata = flowContext.getHeadMetadata();\n        String traceId = headMetadata.getTraceId();\n\n        if (Objects.nonNull(traceId)) {\n            MDC.put(TraceKit.traceName, traceId);\n        }\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        MDC.clear();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - <a href=\"https://iohao.github.io/game/docs/manual/plugin_intro\">业务框架插件</a>。\n * <pre>\n *     ioGame ActionMethodInOut 是业务框架的插件机制，通过这个接口你可以做很多事情，这要看你的想象力有多丰富了。比如：\n *     1. 开发者想记录执行时间比较长的 action 业务\n *     2. 哪个业务方法执行的次数最多\n *     3. 将 userId 保存到 ThreadLocal 中\n *\n *     ioGame 已经提供了一些内置插件，随着时间的推移，插件的数量会不断的增加。开发者如有需要，可扩展一些符合自身业务的插件。\n *     不同的插件提供了不同的关注点，比如我们可以使用调用、监控等插件相互配合，可以让我们在开发阶段就知道是否存在性能问题。\n *     合理利用好各个插件，可以让我们在开发阶段就能知道问题所在，提前发现问题，提前预防问题。\n * </pre>\n * for example\n * <pre>{@code\n * // 业务框架构建器\n * BarSkeletonBuilder builder = ...;\n *\n * // 控制台输出插件，将插件添加到业务框架中\n * var debugInOut = new DebugInOut();\n * builder.addInOut(debugInOut);\n *\n * // action 调用统计插件，将插件添加到业务框架中\n * var statActionInOut = new StatActionInOut();\n * builder.addInOut(statActionInOut);\n *\n * // 业务线程监控插件，将插件添加到业务框架中\n * var threadMonitorInOut = new ThreadMonitorInOut();\n * builder.addInOut(threadMonitorInOut);\n *\n * // 全链路调用日志跟踪插件，将插件添加到业务框架中\n * builder.addInOut(new TraceIdInOut());\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core.flow.internal;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - <a href=\"https://iohao.github.io/game/docs/core/framework\">核心流程处理</a>。\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core.flow;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/BoolValueMethodParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.wrapper.BoolValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.BoolValueList;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * boolean 包装类解析器\n *\n * @author 渔民小镇\n * @date 2023-02-07\n */\nfinal class BoolValueMethodParser implements MethodParser {\n    @Override\n    public Class<?> getActualClazz(ActionCommand.MethodParamResultInfo methodParamResultInfo) {\n        return methodParamResultInfo.isList() ? BoolValueList.class : BoolValue.class;\n    }\n\n    @Override\n    public Object parseParam(byte[] data, ActionCommand.ParamInfo paramInfo) {\n\n        if (paramInfo.isList()) {\n\n            if (Objects.isNull(data)) {\n                return Collections.emptyList();\n            }\n\n            var valueList = DataCodecKit.decode(data, BoolValueList.class);\n            return valueList.values;\n        }\n\n        if (Objects.isNull(data)) {\n            return false;\n        }\n\n        BoolValue boolValue = DataCodecKit.decode(data, BoolValue.class);\n        return boolValue.value;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Object parseData(boolean isList, Object data) {\n        if (isList) {\n            var valueList = new BoolValueList();\n            valueList.values = (List<Boolean>) data;\n            return valueList;\n        }\n\n        /*\n         * 将结果转换为 BoolValue\n         * 注意这里不会检测 methodResult 是否为 null，如果担心 null 问题，\n         * 可以使用 boolean，而不是使用 Boolean\n         */\n        return BoolValue.of((boolean) data);\n    }\n\n    private BoolValueMethodParser() {\n    }\n\n    public static BoolValueMethodParser me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final BoolValueMethodParser ME = new BoolValueMethodParser();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/DefaultMethodParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.wrapper.ByteValueList;\nimport com.iohao.game.common.kit.CollKit;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * 默认解析器\n *\n * @author 渔民小镇\n * @date 2022-06-26\n */\nclass DefaultMethodParser implements MethodParser {\n    @Override\n    public Class<?> getActualClazz(ActionCommand.MethodParamResultInfo methodParamResultInfo) {\n        return methodParamResultInfo.getActualTypeArgumentClazz();\n    }\n\n    @Override\n    public Object parseParam(byte[] data, ActionCommand.ParamInfo paramInfo) {\n        Class<?> actualTypeArgumentClazz = paramInfo.getActualTypeArgumentClazz();\n\n        if (paramInfo.isList()) {\n            if (Objects.isNull(data)) {\n                return Collections.emptyList();\n            }\n\n            ByteValueList byteValueList = DataCodecKit.decode(data, ByteValueList.class);\n\n            if (CollKit.isEmpty(byteValueList.values)) {\n                return Collections.emptyList();\n            }\n\n            return byteValueList.values.stream()\n                    .map(bytes -> DataCodecKit.decode(bytes, actualTypeArgumentClazz))\n                    .toList();\n        }\n\n        if (Objects.isNull(data)) {\n            // 如果配置了 action 参数类型的 Supplier，则通过 Supplier 来创建对象\n            var o = MethodParsers.newObject(actualTypeArgumentClazz);\n            if (Objects.nonNull(o)) {\n                return o;\n            }\n        }\n\n        return DataCodecKit.decode(data, actualTypeArgumentClazz);\n    }\n\n    @Override\n    public Object parseResult(ActionCommand.ActionMethodReturnInfo actionMethodReturnInfo, Object methodResult) {\n        return parseData(actionMethodReturnInfo.isList(), methodResult);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Object parseData(boolean isList, Object data) {\n        if (isList) {\n\n            List<Object> list = (List<Object>) data;\n\n            ByteValueList byteValueList = new ByteValueList();\n            byteValueList.values = list.stream()\n                    .map(DataCodecKit::encode)\n                    .collect(Collectors.toList());\n\n            return byteValueList;\n        }\n\n        return data;\n    }\n\n    @Override\n    public boolean isCustomMethodParser() {\n        return false;\n    }\n\n    private DefaultMethodParser() {\n    }\n\n    public static DefaultMethodParser me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DefaultMethodParser ME = new DefaultMethodParser();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/IntValueMethodParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValueList;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * int 包装类解析器\n *\n * @author 渔民小镇\n * @date 2023-02-10\n */\nfinal class IntValueMethodParser implements MethodParser {\n\n    @Override\n    public Class<?> getActualClazz(ActionCommand.MethodParamResultInfo methodParamResultInfo) {\n        return methodParamResultInfo.isList() ? IntValueList.class : IntValue.class;\n    }\n\n    @Override\n    public Object parseParam(byte[] data, ActionCommand.ParamInfo paramInfo) {\n\n        if (paramInfo.isList()) {\n\n            if (Objects.isNull(data)) {\n                return Collections.emptyList();\n            }\n\n            var valueList = DataCodecKit.decode(data, IntValueList.class);\n            return valueList.values;\n        }\n\n        if (Objects.isNull(data)) {\n            return 0;\n        }\n\n        var intValue = DataCodecKit.decode(data, IntValue.class);\n        return intValue.value;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Object parseData(boolean isList, Object data) {\n        if (isList) {\n            var valueList = new IntValueList();\n            valueList.values = (List<Integer>) data;\n            return valueList;\n        }\n\n        /*\n         * 将结果转换为 IntValue；\n         * 注意这里不会检测 methodResult 是否为 null，如果担心 null 问题，\n         * 可以使用 int，而不是使用 Integer\n         */\n        var intValue = new IntValue();\n        intValue.value = (int) data;\n        return intValue;\n    }\n\n    private IntValueMethodParser() {\n    }\n\n    public static IntValueMethodParser me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final IntValueMethodParser ME = new IntValueMethodParser();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/LongValueMethodParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.wrapper.LongValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.LongValueList;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * long 包装类解析器\n *\n * @author 渔民小镇\n * @date 2023-02-10\n */\nfinal class LongValueMethodParser implements MethodParser {\n\n    @Override\n    public Class<?> getActualClazz(ActionCommand.MethodParamResultInfo methodParamResultInfo) {\n        return methodParamResultInfo.isList() ? LongValueList.class : LongValue.class;\n    }\n\n    @Override\n    public Object parseParam(byte[] data, ActionCommand.ParamInfo paramInfo) {\n        if (paramInfo.isList()) {\n\n            if (Objects.isNull(data)) {\n                return Collections.emptyList();\n            }\n\n            var valueList = DataCodecKit.decode(data, LongValueList.class);\n            return valueList.values;\n        }\n\n        if (Objects.isNull(data)) {\n            return 0L;\n        }\n\n        var longValue = DataCodecKit.decode(data, LongValue.class);\n        return longValue.value;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public Object parseData(boolean isList, Object data) {\n        if (isList) {\n            var valueList = new LongValueList();\n            valueList.values = (List<Long>) data;\n            return valueList;\n        }\n\n        /*\n         * 将结果转换为 LongValue；\n         * 注意这里不会检测 methodResult 是否为 null，如果担心 null 问题，\n         * 可以使用 long，而不是使用 Long\n         */\n        var longValue = new LongValue();\n        longValue.value = (long) data;\n        return longValue;\n    }\n\n    private LongValueMethodParser() {\n    }\n\n    public static LongValueMethodParser me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final LongValueMethodParser ME = new LongValueMethodParser();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/MethodParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\n\n/**\n * action 方法解析：解析方法参数、解析方法返回值\n *\n * @author 渔民小镇\n * @date 2022-06-26\n */\npublic interface MethodParser {\n\n    /**\n     * 得到真实类型\n     * <pre>\n     *     常规的 java class 是指本身，如：\n     *     开发者自定义了一个 StudentPb，在 action 方法上参数声明为 xxx(StudentPb studentPb)\n     *     那么这个值就是 StudentPb\n     * </pre>\n     *\n     * <pre>\n     *     但由于框架现在内置了一些包装类型，如：\n     *     int --> IntValue\n     *     List&lt;Integer&gt; --> IntValueList\n     *\n     *     long --> LongValue\n     *     List&lt;Long&gt; --> LongValueList\n     *\n     *     所以当开发者在 action 方法上参数声明为基础类型时；\n     *     如声明为 int 那么这个值将会是 IntValue\n     *     如声明为 long 那么这个值将会是 LongValue\n     *\n     *     如声明为 List&lt;Integer&gt; 那么这个值将会是 IntValueList\n     *     包装类型相关的以此类推;\n     *\n     *     这么做的目的是为了生成文档时，不与前端产生争议，\n     *     如果提供给前端的文档显示 int ，或许前端同学会懵B，\n     *     当然如果提前与前端同学沟通好这些约定，也不是那么麻烦。\n     *     但实际上如果前端是新来接手项目的，碰见这种情况也会小懵，\n     *     所以为了避免这些小尬，框架在生成文档时，用基础类型的内置包装类型来表示。\n     * </pre>\n     *\n     * @param methodParamResultInfo methodParamResultInfo\n     * @return 真实类型\n     */\n    Class<?> getActualClazz(ActionCommand.MethodParamResultInfo methodParamResultInfo);\n\n    /**\n     * 解析 action 方法参数\n     *\n     * @param data      data\n     * @param paramInfo paramInfo\n     * @return 解析后的数据\n     */\n    Object parseParam(byte[] data, ActionCommand.ParamInfo paramInfo);\n\n    /**\n     * 解析 action 结果 （方法返回值）\n     *\n     * @param actionMethodReturnInfo actionMethodReturnInfo\n     * @param methodResult           当前的返回值\n     * @return 处理后的返回值\n     */\n    default Object parseResult(ActionCommand.ActionMethodReturnInfo actionMethodReturnInfo, Object methodResult) {\n        return parseData(actionMethodReturnInfo.isList(), methodResult);\n    }\n\n    /**\n     * 是否自定义的方法解析器\n     *\n     * @return true 是自定义的\n     */\n    default boolean isCustomMethodParser() {\n        return true;\n    }\n\n    /**\n     * 解析 data 参数\n     *\n     * @param isList true: the data is list\n     * @param data   参数\n     * @return 处理后的返回值\n     * @since 21.24\n     */\n    default Object parseData(boolean isList, Object data) {\n        return data;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/MethodParsers.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.protocol.wrapper.*;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\n/**\n * 关于业务框架中，action 参数相关的包装类\n *\n * @author 渔民小镇\n * @date 2022-06-26\n */\n@UtilityClass\npublic final class MethodParsers {\n    /**\n     * 方法解析器 map\n     * <pre>\n     *     key : 一般用基础类型\n     *     value : 基础类型对应的解析器\n     *\n     *     如 int 与对应的包装类 Integer 在这里规划为基础类型\n     * </pre>\n     */\n    final Map<Class<?>, MethodParser> methodParserMap = new HashMap<>();\n    final Map<Class<?>, Supplier<?>> paramSupplierMap = new HashMap<>();\n\n    /** action 业务方法参数的默认解析器 */\n    @Setter\n    MethodParser methodParser = DefaultMethodParser.me();\n\n    static {\n        init();\n    }\n\n    public void mappingParamSupplier(Class<?> paramClass, Supplier<?> supplier) {\n        paramSupplierMap.put(paramClass, supplier);\n    }\n\n    public void mapping(Class<?> paramClass, MethodParser methodParamParser) {\n        methodParserMap.put(paramClass, methodParamParser);\n    }\n\n    public MethodParser getMethodParser(ActionCommand.ActionMethodReturnInfo actionMethodReturnInfo) {\n        Class<?> methodResultClass = actionMethodReturnInfo.getActualTypeArgumentClazz();\n        return getMethodParser(methodResultClass);\n    }\n\n    public MethodParser getMethodParser(ActionCommand.ParamInfo paramInfo) {\n        Class<?> actualTypeArgumentClazz = paramInfo.getActualTypeArgumentClazz();\n        return getMethodParser(actualTypeArgumentClazz);\n    }\n\n    public MethodParser getMethodParser(Class<?> paramClazz) {\n        return methodParserMap.getOrDefault(paramClazz, methodParser);\n    }\n\n    public void clear() {\n        methodParserMap.clear();\n        paramSupplierMap.clear();\n    }\n\n    public boolean containsKey(Class<?> clazz) {\n        return methodParserMap.containsKey(clazz);\n    }\n\n    public Set<Class<?>> keySet() {\n        return methodParserMap.keySet();\n    }\n\n    private void init() {\n        // 表示在 action 参数中，遇见 int 类型的参数，用 IntValueMethodParser 来解析\n        mapping(int.class, IntValueMethodParser.me());\n        mapping(Integer.class, IntValueMethodParser.me());\n\n        // 表示在 action 参数中，遇见 long 类型的参数，用 LongValueMethodParser 来解析\n        mapping(long.class, LongValueMethodParser.me());\n        mapping(Long.class, LongValueMethodParser.me());\n\n        // 表示在 action 参数中，遇见 String 类型的参数，用 StringValueMethodParser 来解析\n        mapping(String.class, StringValueMethodParser.me());\n\n        // 表示在 action 参数中，遇见 boolean 类型的参数，用 BoolValueMethodParser 来解析\n        mapping(boolean.class, BoolValueMethodParser.me());\n        mapping(Boolean.class, BoolValueMethodParser.me());\n\n        /*\n         * 这里注册是为了顺便使用 containsKey 方法，因为生成文档的时候要用到短名字\n         * 当然也可以使用 instanceof 来做这些，但似乎没有这种方式优雅\n         */\n        mapping(IntValue.class, DefaultMethodParser.me(), IntValue::new);\n        mapping(IntValueList.class, DefaultMethodParser.me(), IntValueList::new);\n\n        mapping(LongValue.class, DefaultMethodParser.me(), LongValue::new);\n        mapping(LongValueList.class, DefaultMethodParser.me(), LongValueList::new);\n\n        mapping(BoolValue.class, DefaultMethodParser.me(), BoolValue::new);\n        mapping(BoolValueList.class, DefaultMethodParser.me(), BoolValueList::new);\n\n        mapping(StringValue.class, DefaultMethodParser.me(), StringValue::new);\n        mapping(StringValueList.class, DefaultMethodParser.me(), StringValueList::new);\n    }\n\n    Object newObject(Class<?> paramClass) {\n        if (paramSupplierMap.containsKey(paramClass)) {\n            return paramSupplierMap.get(paramClass).get();\n        }\n\n        return null;\n    }\n\n    private void mapping(Class<?> paramClass, MethodParser methodParamParser, Supplier<?> supplier) {\n        mapping(paramClass, methodParamParser);\n\n        /*\n         * 使用原生 pb 如果值为 0，在 jprotobuf 中会出现 nul 的情况，为了避免这个问题\n         * 如果业务参数为 null，当解析到对应的类型时，则使用 Supplier 来创建对象\n         *\n         * 具体使用可参考 DefaultMethodParser\n         */\n        mappingParamSupplier(paramClass, supplier);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/StringValueMethodParser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.wrapper.StringValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.StringValueList;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-05\n */\nfinal class StringValueMethodParser implements MethodParser {\n    @Override\n    public Class<?> getActualClazz(ActionCommand.MethodParamResultInfo methodParamResultInfo) {\n        return methodParamResultInfo.isList() ? StringValueList.class : StringValue.class;\n    }\n\n    @Override\n    public Object parseParam(byte[] data, ActionCommand.ParamInfo paramInfo) {\n\n        if (paramInfo.isList()) {\n\n            if (Objects.isNull(data)) {\n                return Collections.emptyList();\n            }\n\n            StringValueList valueList = DataCodecKit.decode(data, StringValueList.class);\n            return valueList.values;\n        }\n\n        if (Objects.isNull(data)) {\n            return null;\n        }\n\n        StringValue stringValue = DataCodecKit.decode(data, StringValue.class);\n        return stringValue.value;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public Object parseData(boolean isList, Object data) {\n        if (isList) {\n            StringValueList valueList = new StringValueList();\n            valueList.values = (List<String>) data;\n            return valueList;\n        }\n\n        StringValue stringValue = new StringValue();\n        stringValue.value = String.valueOf(data);\n        return stringValue;\n    }\n\n    private StringValueMethodParser() {\n    }\n\n    public static StringValueMethodParser me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final StringValueMethodParser ME = new StringValueMethodParser();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/parser/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - action 参数解析器，解析方法参数、解析方法返回值。\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core.flow.parser;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - core\n *\n * @author 渔民小镇\n * @date 2024-08-05\n */\npackage com.iohao.game.action.skeleton.core;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/runner/InternalRunner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.runner;\n\nimport com.iohao.game.action.skeleton.pulse.runner.CreatePulsesRunner;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 框架内置的一些 Runner\n *\n * @author 渔民小镇\n * @date 2023-04-23\n */\nfinal class InternalRunner {\n    final List<Runner> runnerList = new ArrayList<>();\n\n    InternalRunner() {\n        runnerList.add(new CreatePulsesRunner());\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/runner/Runner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.runner;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\n\n/**\n * Runner 机制，会在逻辑服与 Broker（游戏网关）建立连接之前（onStart）、之后（onStartAfter）分别触发一次。<a href=\"https://iohao.github.io/game/docs/core/runner\">相关文档</a>\n * <pre>\n *     1.在逻辑服与 Broker（游戏网关）建立连接之前调用一次，触发 {@link Runner#onStart(BarSkeleton)} 方法。\n *     2.在逻辑服将信息注册到 Broker（游戏网关）后调用一次，触发 {@link Runner#onStartAfter(BarSkeleton)} 方法。\n * </pre>\n * for example\n * <pre>{@code\n * // 路由访问权限以 Runner 机制扩展\n * public class ExternalAccessAuthenticationRunner implements Runner {\n *     @Override\n *     public void onStart(BarSkeleton skeleton) {\n *\n *         var accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;\n *         // 表示登录才能访问业务方法\n *         accessAuthenticationHook.setVerifyIdentity(true);\n *         // 添加不需要登录（身份验证）也能访问的业务方法 (action)\n *         accessAuthenticationHook.addIgnoreAuthenticationCmd(1, 1);\n *         // 添加不需要登录（身份验证）也能访问的主路由（范围）\n *         accessAuthenticationHook.addIgnoreAuthenticationCmd(2);\n *\n *         // 拒绝主路由为 10 的访问请求\n *         accessAuthenticationHook.addRejectionCmd(10);\n *         // 拒绝主路由为 11、子路由为 1 的访问请求\n *         accessAuthenticationHook.addRejectionCmd(11, 1);\n *     }\n * }\n *\n * // 游戏对外服\n * public class MyExternalServer extends ExternalBrokerClientStartup {\n *     @Override\n *     public BarSkeleton createBarSkeleton() {\n *         // 游戏对外服不需要业务框架，这里给个空的\n *         BarSkeletonBuilder builder = BarSkeleton.newBuilder();\n *\n *         // 路由访问权限以 Runner 机制扩展\n *         builder.addRunner(new ExternalAccessAuthenticationRunner());\n *\n *         return builder.build();\n *     }\n * }\n * }\n * </pre>\n * for example\n * <pre>{@code\n * BarSkeletonBuilder builder = ...\n *\n * builder.addRunner(new Runner() {\n *   @Override\n *   public void onStart(BarSkeleton skeleton) {\n *       log.info(\"在逻辑服与 Broker（游戏网关）建立连接之前调用一次\");\n *   }\n *\n *   @Override\n *   public void onStartAfter(BarSkeleton skeleton) {\n *       log.info(\"在逻辑服与 Broker（游戏网关）建立连接之后调用一次\");\n *   }\n * });\n *\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-04-23\n */\npublic interface Runner {\n    /**\n     * 在逻辑服与 Broker（游戏网关）建立连接之前调用一次。\n     * <pre>\n     *     此时还不能与 Broker（游戏网关）通信。\n     * </pre>\n     *\n     * @param skeleton 业务框架\n     */\n    void onStart(BarSkeleton skeleton);\n\n    /**\n     * 在逻辑服与 Broker（游戏网关）建立连接之后调用一次。\n     * <pre>\n     *     可以与 Broker（游戏网关）通信了。\n     *     如果没有特殊需求的，使用 onStart 方法就可以了。\n     * </pre>\n     *\n     * @param skeleton 业务框架\n     */\n    default void onStartAfter(BarSkeleton skeleton) {\n    }\n\n    /**\n     * runner name\n     *\n     * @return name\n     */\n    default String name() {\n        return this.getClass().getName();\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/runner/Runners.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.runner;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\n\n/**\n * Runners 管理器\n *\n * @author 渔民小镇\n * @date 2023-04-23\n * @see Runner\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class Runners {\n    final List<Runner> runnerList = new CopyOnWriteArrayList<>();\n    final AtomicBoolean onStart = new AtomicBoolean(false);\n    final AtomicBoolean onStartAfter = new AtomicBoolean(false);\n\n    @Setter\n    BarSkeleton barSkeleton;\n\n    public Runners() {\n        // 内置一些 runner\n        InternalRunner internalRunner = new InternalRunner();\n        this.runnerList.addAll(internalRunner.runnerList);\n    }\n\n    public void addRunner(Runner runner) {\n\n        if (this.onStart.get()) {\n            // 运行中，不能添加 Runner 了。\n            ThrowKit.ofRuntimeException(\"Cannot add Runner while running.\");\n        }\n\n        Objects.requireNonNull(runner);\n\n        this.runnerList.add(runner);\n    }\n\n    /** 启动 runner 机制 onStart 方法 */\n    public void onStart() {\n        if (this.onStart.get()) {\n            return;\n        }\n\n        if (this.onStart.compareAndSet(false, true)) {\n            this.runnerList.forEach(runner -> runner.onStart(this.barSkeleton));\n        }\n    }\n\n    /** 启动 runner 机制 onStartAfter 方法 */\n    public void onStartAfter() {\n        if (this.onStartAfter.get()) {\n            return;\n        }\n\n        if (this.onStartAfter.compareAndSet(false, true)) {\n            TaskKit.runOnceSecond(() -> {\n                // 延迟 1 秒执行，防止没连接到服务器， 或者将来增加一个注册回调的 Processor，目前先暂时这样\n                this.runnerList.forEach(runner -> runner.onStartAfter(this.barSkeleton));\n            });\n        }\n    }\n\n    /**\n     * Runners Name\n     *\n     * @return Runners Name\n     */\n    public List<String> listRunnerName() {\n        return this.runnerList.stream()\n                .map(Runner::name)\n                .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/core/runner/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - <a href=\"https://iohao.github.io/game/docs/core/runner\">Runner 扩展机制</a>，该机制类似于 Spring CommandLineRunner 的启动项，它能够在逻辑服务器启动之后调用一次 Runner 接口实现类，让开发者能够通过实现 Runner 接口来扩展自身的系统。\n * <p>\n * Runner 机制，会在逻辑服与 Broker（游戏网关）建立连接之前、之后分别触发一次对应的方法。\n * <p>\n * for example\n * <pre>{@code\n * // 路由访问权限以 Runner 机制扩展\n * public class ExternalAccessAuthenticationRunner implements Runner {\n *     @Override\n *     public void onStart(BarSkeleton skeleton) {\n *\n *         var accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;\n *         // 表示登录才能访问业务方法\n *         accessAuthenticationHook.setVerifyIdentity(true);\n *         // 添加不需要登录（身份验证）也能访问的业务方法 (action)\n *         accessAuthenticationHook.addIgnoreAuthenticationCmd(1, 1);\n *         // 添加不需要登录（身份验证）也能访问的主路由（范围）\n *         accessAuthenticationHook.addIgnoreAuthenticationCmd(2);\n *\n *         // 拒绝主路由为 10 的访问请求\n *         accessAuthenticationHook.addRejectionCmd(10);\n *         // 拒绝主路由为 11、子路由为 1 的访问请求\n *         accessAuthenticationHook.addRejectionCmd(11, 1);\n *     }\n * }\n *\n * // MyRunner\n * public class MyRunner implements Runner {\n *     @Override\n *     public void onStart(BarSkeleton skeleton) {\n *         ... ... 省略部分代码\n *     }\n * }\n *\n * // 游戏对外服\n * public class MyExternalServer extends ExternalBrokerClientStartup {\n *     @Override\n *     public BarSkeleton createBarSkeleton() {\n *         // 游戏对外服不需要业务框架，这里给个空的\n *         BarSkeletonBuilder builder = BarSkeleton.newBuilder();\n *\n *         // 路由访问权限以 Runner 机制扩展\n *         builder.addRunner(new ExternalAccessAuthenticationRunner());\n *         // MyRunner\n *         builder.addRunner(new MyRunner());\n *\n *         return builder.build();\n *     }\n * }\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2024-06-05\n * @see com.iohao.game.action.skeleton.core.runner.Runner\n */\npackage com.iohao.game.action.skeleton.core.runner;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/AbstractEventBusRunner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\n/**\n * 分布式事件总线 Runner\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n * @deprecated 请使用 {@link EventBusRunner} 代替\n */\n@Deprecated\npublic abstract class AbstractEventBusRunner implements EventBusRunner {\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBrokerClientMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Collection;\nimport java.util.Objects;\n\n/**\n * 事件总线逻辑服相关信息\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\n@Data\npublic final class EventBrokerClientMessage implements Serializable {\n    final String appName;\n    final String tag;\n    final String brokerClientType;\n    final String brokerClientId;\n    transient boolean remote;\n    /** 订阅者主题 */\n    transient EventTopicMessage eventTopicMessage;\n\n    public EventBrokerClientMessage(String appName, String tag, String brokerClientType, String brokerClientId) {\n        this.appName = appName;\n        this.tag = tag;\n        this.brokerClientType = brokerClientType;\n        this.brokerClientId = Objects.requireNonNull(brokerClientId);\n    }\n\n    public Collection<String> getTopics() {\n        return eventTopicMessage.topicSet();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof EventBrokerClientMessage that)) {\n            return false;\n        }\n\n        return brokerClientId.equals(that.brokerClientId);\n    }\n\n    @Override\n    public int hashCode() {\n        return brokerClientId.hashCode();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBus.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\n\nimport java.util.*;\n\n/**\n * 事件总线 EventBus，EventBus、业务框架、逻辑服三者是 1:1:1 的关系。\n * <p>\n * 发布事件\n * <pre>\n *     在发布事件时\n *     1. 如果相关订阅者在同进程内，可控制同步和异步发送。\n *     2. 如果相关订阅者不在同一个进程内，而是分布在不同的进程中，则只能异步发送（即使使用了同步的方法来发布事件）。\n *\n *     这里的【同步】指的是：发布事件时，相关订阅者执行完成后，主逻辑才会继续往下走。\n *     这里的【异步】指的是：发布事件时，主逻辑不会阻塞，相关订阅者会在其他线程中执行。\n *\n *     无论是同步或者是异步，相关订阅者在执行逻辑服时，默认是线程安全的；这是因为订阅者 {@link EventSubscribe} 默认使用的是用户线程执行器。\n * </pre>\n * <p>\n * 关于获取 EventBus 的相关示例\n * <p>\n * for example 1 - 通过 FlowContext 获取对应的 eventBus\n * <pre>{@code\n * EventBus eventBus = flowContext.getEventBus();\n * eventBus.fire(userLoginEventMessage);\n * }\n * </pre>\n * for example 2 - 通过逻辑服 id 获取对应的 eventBus\n * <pre>{@code\n * BrokerClientContext brokerClientContext = flowContext.getBrokerClientContext();\n * String id = brokerClientContext.getId();\n * EventBus eventBus = EventBusRegion.getEventBus(id);\n * }\n * </pre>\n * for example 3 - 通过业务框架获取对应的 eventBus\n * <pre>{@code\n * BarSkeleton barSkeleton = ...\n * EventBus eventBus = barSkeleton.option(SkeletonAttr.eventBus);\n * }\n * </pre>\n * for example 4 - 在初始化时，自己保存一下引用\n * <pre>{@code\n * public BarSkeleton createBarSkeleton() {\n *     // 业务框架构建器\n *     var builder = ...\n *     // 游戏逻辑服添加 EventBusRunner，用于处理 EventBus 相关业务\n *     builder.addRunner(new EventBusRunner() {\n *         @Override\n *         public void registerEventBus(EventBus eventBus, BarSkeleton skeleton) {\n *            // 这里保存一下 EventBus 的引用\n *         }\n *     });\n * }\n * }\n * </pre>\n * fire 系列提供了多个种类的事件发布机制\n * <pre>\n *     1. fire 发送事件给订阅者，这些订阅者包括\n *         a. 给当前进程所有逻辑服的订阅者发送事件消息。\n *         b. 给其他进程的订阅者发送事件消息。\n *     2. fireLocal 给当前进程所有逻辑服的订阅者发送事件消息\n *     3. fireMe 仅给当前 EventBus 的订阅者发送事件消息\n *     4. fireAny 发送事件给订阅者，这些订阅者包括\n *         a. 给当前进程所有逻辑服的订阅者发送事件消息。\n *         b. 给其他进程的订阅者发送事件消息。\n *         c. 当有同类型的多个逻辑服实例时，只会给同类型其中的一个逻辑服发送事件。\n *\n *     fire 系列提供了多个种类的事件发布机制，以上方法默认是异步的，而相关同步方法则以 fireXXXSync 命名。\n * </pre>\n * 便捷使用 - {@link FlowContext}\n * <pre>\n *     除了可以通过 EventBus 发布事件外，框架还在 {@link FlowContext} 中提供了 EventBus 的相关方法。\n *     FlowContext 内部使用 EventBus 来发布事件。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @see FlowContext#getEventBus()\n * @see EventBusRegion\n * @see FlowContext\n * @since 21\n */\npublic interface EventBus {\n    /**\n     * EventBus id。EventBus、业务框架、逻辑服三者是 1:1:1 的关系，默认该 id 是逻辑服的 id;\n     *\n     * @return id\n     */\n    String getId();\n\n    /**\n     * 注册订阅者\n     *\n     * @param eventBusSubscriber 订阅者\n     */\n    void register(Object eventBusSubscriber);\n\n    /**\n     * 事件消息所对应的订阅者\n     *\n     * @param eventBusMessage 事件消息\n     * @return 所对应的订阅者\n     */\n    Collection<Subscriber> listSubscriber(EventBusMessage eventBusMessage);\n\n    /**\n     * 当前 eventBus 订阅的所有事件源主题\n     *\n     * @return 当前 eventBus 订阅的所有事件源主题\n     */\n    Set<String> listTopic();\n\n    /**\n     * [异步] 发送事件给所有订阅者\n     * <pre>\n     *     1 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 给其他进程的订阅者发送事件消息\n     * </pre>\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fire(EventBusMessage eventBusMessage);\n\n    /**\n     * [同步] 发送事件给所有订阅者\n     * <pre>\n     *     1 [同步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 [异步] 给其他进程的订阅者发送事件消息\n     *\n     *     注意，这里的同步仅指当前进程订阅者的同步，对其他进程中的订阅者无效（处理远程订阅者使用的是异步）。\n     * </pre>\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireSync(EventBusMessage eventBusMessage);\n\n    /**\n     * [异步] 发送事件给所有订阅者\n     * <pre>\n     *     1 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 给其他进程的订阅者发送事件消息\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fire(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fire(eventBusMessage);\n    }\n\n    /**\n     * [同步] 发送事件给所有订阅者\n     * <pre>\n     *     1 [同步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *     2 [异步] 给其他进程的订阅者发送事件消息\n     *\n     *     注意，这里的同步仅指当前进程订阅者的同步，对其他进程中的订阅者无效（处理远程订阅者使用的是异步）。\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireSync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireSync(eventBusMessage);\n    }\n\n    /**\n     * [异步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *\n     * @param eventSource 事件源\n     */\n    default void fireLocal(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireLocal(eventBusMessage);\n    }\n\n    /**\n     * [异步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireLocal(EventBusMessage eventBusMessage);\n\n    /**\n     * [同步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *\n     * @param eventSource 事件源\n     */\n    default void fireLocalSync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireLocalSync(eventBusMessage);\n    }\n\n    /**\n     * [同步] 给当前进程所有逻辑服的订阅者发送事件消息\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireLocalSync(EventBusMessage eventBusMessage);\n\n    /**\n     * [异步] 仅给当前 EventBus 的订阅者发送事件消息\n     * <pre>\n     *     已注册到 {@link EventBus#register(Object)}  的订阅者\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireMe(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireMe(eventBusMessage);\n    }\n\n    /**\n     * [异步] 仅给当前 EventBus 的订阅者发送事件消息\n     * <pre>\n     *     已注册到 {@link EventBus#register(Object)}  的订阅者\n     * </pre>\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireMe(EventBusMessage eventBusMessage);\n\n    /**\n     * [同步] 仅给当前 EventBus 的订阅者发送事件消息\n     * <pre>\n     *     已注册到 {@link EventBus#register(Object)} 的订阅者\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireMeSync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireMeSync(eventBusMessage);\n    }\n\n    /**\n     * [同步] 仅给当前 EventBus 的订阅者发送事件消息\n     * <pre>\n     *     已注册到 {@link EventBus#register(Object)} 的订阅者\n     * </pre>\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireMeSync(EventBusMessage eventBusMessage);\n\n    /**\n     * [异步] 给当前进程的订阅者和远程进程的订阅者送事件消息，如果同类型逻辑服存在多个，只会给其中一个实例发送。\n     * <pre>\n     *     假设现在有一个发放奖励的邮件逻辑服，我们启动了两个（或者说多个）邮件逻辑服实例来处理业务。\n     *     当我们使用 fireAny 方法发送事件时，只会给其中一个实例发送事件。\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireAny(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireAny(eventBusMessage);\n    }\n\n    /**\n     * [异步] 给当前进程的订阅者和远程进程的订阅者送事件消息，如果同类型逻辑服存在多个，只会给其中一个实例发送。\n     * <pre>\n     *     假设现在有一个发放奖励的邮件逻辑服，我们启动了两个（或者说多个）邮件逻辑服实例来处理业务。\n     *     当我们使用 fireAny 方法发送事件时，只会给其中一个实例发送事件。\n     * </pre>\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireAny(EventBusMessage eventBusMessage);\n\n    /**\n     * [同步] 给当前进程的订阅者和远程进程的订阅者送事件消息，如果同类型逻辑服存在多个，只会给其中一个实例发送。\n     * <pre>\n     *     假设现在有一个发放奖励的邮件逻辑服，我们启动了两个（或者说多个）邮件逻辑服实例来处理业务。\n     *     当我们使用 fireAny 方法发送事件时，只会给其中一个实例发送事件。\n     * </pre>\n     *\n     * @param eventSource 事件源\n     */\n    default void fireAnySync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireAnySync(eventBusMessage);\n    }\n\n    /**\n     * [同步] 给当前进程的订阅者和远程进程的订阅者送事件消息，如果同类型逻辑服存在多个，只会给其中一个实例发送。\n     * <pre>\n     *     假设现在有一个发放奖励的邮件逻辑服，我们启动了两个（或者说多个）邮件逻辑服实例来处理业务。\n     *     当我们使用 fireAny 方法发送事件时，只会给其中一个实例发送事件。\n     * </pre>\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireAnySync(EventBusMessage eventBusMessage);\n\n    /**\n     * [异步] 给当前进程其他逻辑服的订阅者发送事件消息，不包括当前 EventBus。\n     *\n     * @param eventSource 事件源\n     */\n    default void fireLocalNeighbor(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireLocalNeighbor(eventBusMessage);\n    }\n\n    /**\n     * [异步] 给当前进程其他逻辑服的订阅者发送事件消息，不包括当前 EventBus。\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireLocalNeighbor(EventBusMessage eventBusMessage);\n\n    /**\n     * [同步] 给当前进程其他逻辑服的订阅者发送事件消息，不包括当前 EventBus。\n     *\n     * @param eventSource 事件源\n     */\n    default void fireLocalNeighborSync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireLocalNeighborSync(eventBusMessage);\n    }\n\n    /**\n     * [同步] 给当前进程其他逻辑服的订阅者发送事件消息，不包括当前 EventBus。\n     *\n     * @param eventBusMessage 事件消息\n     */\n    void fireLocalNeighborSync(EventBusMessage eventBusMessage);\n\n    /**\n     * set 订阅者线程执行器选择策略\n     *\n     * @param subscribeExecutorStrategy 订阅者线程执行器选择策略\n     */\n    void setSubscribeExecutorStrategy(SubscribeExecutorStrategy subscribeExecutorStrategy);\n\n    /**\n     * get 订阅者线程执行器选择策略\n     *\n     * @return 订阅者线程执行器选择策略\n     */\n    SubscribeExecutorStrategy getSubscribeExecutorStrategy();\n\n    /**\n     * set SubscriberInvokeCreator\n     *\n     * @param subscriberInvokeCreator SubscriberInvokeCreator\n     */\n    void setSubscriberInvokeCreator(SubscriberInvokeCreator subscriberInvokeCreator);\n\n    /**\n     * set 事件消息创建者，EventBusMessage creator\n     *\n     * @param eventBusMessageCreator EventBusMessageCreator\n     */\n    void setEventBusMessageCreator(EventBusMessageCreator eventBusMessageCreator);\n\n    /**\n     * set 事件监听器\n     *\n     * @param eventBusListener 事件监听器\n     */\n    void setEventBusListener(EventBusListener eventBusListener);\n\n    /**\n     * get 事件监听器\n     *\n     * @return 事件监听器\n     */\n    EventBusListener getEventBusListener();\n\n    /**\n     * set 事件总线逻辑服相关信息\n     *\n     * @param eventBrokerClientMessage 事件总线逻辑服相关信息\n     */\n    void setEventBrokerClientMessage(EventBrokerClientMessage eventBrokerClientMessage);\n\n    /**\n     * 当前服务器上下文（逻辑服）\n     *\n     * @param brokerClientContext 当前服务器上下文（逻辑服）\n     */\n    void setBrokerClientContext(BrokerClientContext brokerClientContext);\n\n    /**\n     * set 线程执行器管理域\n     *\n     * @param executorRegion 线程执行器管理域\n     */\n    void setExecutorRegion(ExecutorRegion executorRegion);\n\n    /**\n     * get 线程执行器管理域\n     *\n     * @return 线程执行器管理域\n     */\n    ExecutorRegion getExecutorRegion();\n\n    /**\n     * get 事件消息创建者\n     *\n     * @return 事件消息创建者\n     */\n    EventBusMessageCreator getEventBusMessageCreator();\n\n    /**\n     * get 事件总线逻辑服相关信息\n     *\n     * @return 事件总线逻辑服相关信息\n     */\n    EventBrokerClientMessage getEventBrokerClientMessage();\n\n    /**\n     * 创建事件消息\n     *\n     * @param eventSource 事件源\n     * @return 事件消息\n     */\n    default EventBusMessage createEventBusMessage(Object eventSource) {\n        return this.getEventBusMessageCreator().create(eventSource);\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBusFireType.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\n/**\n * 发布事件时，所触发的类型\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @see EventBus\n * @since 21\n */\npublic interface EventBusFireType {\n    /**\n     * 当前进程\n     * <pre>\n     *     表示所使用的 EventBus 中的订阅者接收了事件消息。\n     *\n     *     举例：\n     *     EventBus 是逻辑服事件总线。 EventBus、业务框架、逻辑服三者是 1:1:1 的关系。\n     *\n     *     假设我们有两个游戏逻辑服，分别是 UserServer 和 EmailServer；那么每个游戏逻辑服会对应一个 EventBus，\n     *     假设 user EventBus 使用 fireMe 发布事件，只会被 user 上注册的订阅者接收到消息。\n     *     即使其他游戏逻辑服订阅了该事件源，也不会接收到消息。\n     * </pre>\n     */\n    int fireMe = 1;\n    /** 当前进程的其他 EventBus */\n    int fireLocalNeighbor = 2;\n    /** 当前进程所有订阅者收了事件消息 */\n    int fireLocal = 4;\n    /**\n     * 远程\n     * <pre>\n     *     表示远程有订阅者接收了事件消息。（通常指的是跨服）\n     * </pre>\n     */\n    int fireRemote = 8;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBusListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\n/**\n * 事件监听器，触发条件，1.订阅者抛出未捕获的异常时、2.事件消息没有对应的订阅者时。\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\npublic interface EventBusListener {\n    /**\n     * 订阅者异常处理\n     *\n     * @param e               e\n     * @param eventSource     事件源\n     * @param eventBusMessage 事件消息\n     */\n    void invokeException(Throwable e, Object eventSource, EventBusMessage eventBusMessage);\n\n    /**\n     * 事件消息没有对应的订阅者时，触发的监听回调\n     * <pre>\n     *     注意，默认情况下只有调用 {@link EventBus#fire} 系列方法时才会检测。\n     * </pre>\n     *\n     * @param eventBusMessage 事件消息\n     * @param eventBus        eventBus\n     */\n    void emptySubscribe(EventBusMessage eventBusMessage, EventBus eventBus);\n\n    static EventBusListener defaultInstance() {\n        return DefaultEventBusListener.me();\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBusMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.Collection;\n\n/**\n * 事件消息\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class EventBusMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -1661393033598374515L;\n    /**\n     * threadIndex 主要作用是确定使用哪个线程执行器。\n     * <pre>\n     *     当该值为 0 时，框架会分配一个值。\n     * </pre>\n     */\n    long threadIndex;\n    String traceId;\n    /** 事件源 */\n    Object eventSource;\n    /** 其他进程的信息 */\n    Collection<EventBrokerClientMessage> eventBrokerClientMessages;\n    @Getter(AccessLevel.PACKAGE)\n    transient int fireType;\n\n    /**\n     * 已经触发的订阅者类型是否存在\n     *\n     * @param fireType {@link EventBusFireType}\n     * @return true 存在\n     * @see EventBusFireType\n     */\n    public boolean containsFireType(int fireType) {\n        return (this.fireType & fireType) == fireType;\n    }\n\n    /**\n     * 添加已经触发的订阅者类型\n     *\n     * @param fireType {@link EventBusFireType}\n     * @see EventBusFireType\n     */\n    void addFireType(int fireType) {\n        this.fireType |= fireType;\n    }\n\n    boolean emptyFireType() {\n        return fireType == 0;\n    }\n\n    String getTopic() {\n        return this.eventSource.getClass().getName();\n    }\n\n    Class<?> getTopicClass() {\n        return this.eventSource.getClass();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBusMessageCreator.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\n/**\n * 事件消息创建者，EventBusMessage creator\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\npublic interface EventBusMessageCreator {\n    EventBusMessage create(Object eventSource);\n\n    static EventBusMessageCreator defaultInstance() {\n        return DefaultEventBusMessageCreator.me();\n    }\n}\n\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBusRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\n/**\n * 事件总线管理域\n * <pre>\n *     1. 管理其他进程的订阅者信息。\n *     2. 如果一个进程中启动了多少逻辑服，那么多个逻辑服的订阅者会添加到这里。\n *\n *     如果只想获取某个逻辑服中的订阅者集合，可通过 {@code EventBus.listSubscriber} 方法得到\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\n@UtilityClass\npublic final class EventBusRegion {\n    public EventBus getEventBus(String brokerClientId) {\n        return EventBusLocalRegion.getEventBus(brokerClientId);\n    }\n\n    void addLocal(EventBus eventBus) {\n        EventBusLocalRegion.addLocal(eventBus);\n\n        EventBusAnyTagRegion.add(eventBus.getEventBrokerClientMessage());\n    }\n\n    Stream<EventBus> streamLocalEventBus() {\n        return EventBusLocalRegion.streamEventBus();\n    }\n\n    boolean hasLocalNeighbor() {\n        return EventBusLocalRegion.hasLocalNeighbor();\n    }\n\n    /**\n     * 根据事件消息，获取当前进程所有的订阅者\n     *\n     * @param eventBusMessage 事件消息\n     * @return 当前进程所有的订阅者\n     */\n    List<Subscriber> listLocalSubscriber(EventBusMessage eventBusMessage) {\n        return EventBusLocalRegion.listLocalSubscriber(eventBusMessage);\n    }\n\n    public void loadRemoteEventTopic(EventBrokerClientMessage eventBrokerClientMessage) {\n        EventBusRemoteRegion.loadRemoteEventTopic(eventBrokerClientMessage);\n\n        EventBusAnyTagRegion.add(eventBrokerClientMessage);\n    }\n\n    public void unloadRemoteTopic(EventBrokerClientMessage eventBrokerClientMessage) {\n        EventBusRemoteRegion.unloadRemoteTopic(eventBrokerClientMessage);\n\n        EventBusAnyTagRegion.remove(eventBrokerClientMessage);\n    }\n\n    Set<EventBrokerClientMessage> listRemoteEventBrokerClientMessage(EventBusMessage eventBusMessage) {\n        return EventBusRemoteRegion.listRemoteEventBrokerClientMessage(eventBusMessage);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBusRunner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.SkeletonAttr;\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport com.iohao.game.action.skeleton.core.runner.Runner;\nimport com.iohao.game.action.skeleton.protocol.processor.SimpleServerInfo;\n\nimport java.util.Set;\n\n/**\n * 分布式事件总线 Runner，将 EventBusRunner 添加到业务框架后，分布式事件总线相关功能才会生效。\n * <pre>{@code\n * // 通过业务框架的 addRunner 方法来扩展分布式事件总线相关内容 （Runner 扩展机制），我们将 UserLoginEventMessage、EmailEventBusSubscriber 注册到 EventBus 中。\n * public class MyLogicStartup extends AbstractBrokerClientStartup {\n *     ... ...省略部分代码\n *\n *     @Override\n *     public BarSkeleton createBarSkeleton() {\n *         // 业务框架构建器\n *         BarSkeletonBuilder builder = ...\n *\n *         // 开启分布式事件总线。逻辑服添加 EventBusRunner，用于处理 EventBus 相关业务\n *         builder.addRunner(new EventBusRunner() {\n *             @Override\n *             public void registerEventBus(EventBus eventBus, BarSkeleton skeleton) {\n *             }\n *         });\n *\n *         return builder.build();\n *     }\n * }\n * }</pre>\n * 注意事项\n * <pre>\n *     如果你的逻辑服没有任何订阅者，只是发送事件，也是需要配置 EventBusRunner 的，这是因为事件总线是按需要加载的功能。\n *     ioGame 功能特性很多，但不是每个项目都需要这些功能。按需加载有很多好处，比如 email 逻辑服后续的业务不想参与任何订阅了，那么把这个 Runner 注释掉就行了。其他代码不用改，这样也不会占用资源。\n *     所以，需要将 EventBusRunner 添加到业务框架后，分布式事件总线相关功能才会生效。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-06-06\n * @since 21.10\n */\npublic interface EventBusRunner extends Runner {\n    @Override\n    default void onStart(BarSkeleton skeleton) {\n        // BrokerClient，当前逻辑服引用\n        BrokerClientContext brokerClientContext = skeleton.option(SkeletonAttr.brokerClientContext);\n        String brokerClientId = brokerClientContext.getId();\n\n        EventBrokerClientMessage eventBrokerClientMessage = getEventBrokerClientMessage(brokerClientContext);\n\n        // EventBus 是逻辑服事件总线。 EventBus、业务框架、逻辑服三者是 1:1:1 的关系。\n        EventBus eventBus = ofEventBus(brokerClientId);\n        skeleton.option(SkeletonAttr.eventBus, eventBus);\n\n        // EventBus 默认设置\n        eventBus.setSubscribeExecutorStrategy(SubscribeExecutorStrategy.defaultInstance());\n        eventBus.setSubscriberInvokeCreator(SubscriberInvokeCreator.defaultInstance());\n        eventBus.setEventBusMessageCreator(EventBusMessageCreator.defaultInstance());\n        eventBus.setEventBusListener(EventBusListener.defaultInstance());\n        eventBus.setExecutorRegion(skeleton.getExecutorRegion());\n\n        eventBus.setBrokerClientContext(brokerClientContext);\n        eventBus.setEventBrokerClientMessage(eventBrokerClientMessage);\n\n        // EventBus 注册订阅者\n        this.registerEventBus(eventBus, skeleton);\n\n        Set<String> topic = eventBus.listTopic();\n        eventBrokerClientMessage.setEventTopicMessage(new EventTopicMessage(topic));\n\n        if (eventBus instanceof DefaultEventBus defaultEventBus) {\n            defaultEventBus.setStatus(EventBusStatus.run);\n        }\n\n        EventBusRegion.addLocal(eventBus);\n    }\n\n    /**\n     * new EventBus\n     *\n     * @param id eventBus id\n     * @return EventBus\n     */\n    default EventBus ofEventBus(String id) {\n        return new DefaultEventBus(id);\n    }\n\n    private EventBrokerClientMessage getEventBrokerClientMessage(BrokerClientContext brokerClientContext) {\n        SimpleServerInfo simpleServerInfo = brokerClientContext.getSimpleServerInfo();\n        String id = simpleServerInfo.getId();\n        String appName = simpleServerInfo.getName();\n        String tag = simpleServerInfo.getTag();\n        String typeName = simpleServerInfo.getBrokerClientType();\n\n        return new EventBrokerClientMessage(appName, tag, typeName, id);\n    }\n\n    /**\n     * 可在此方法中注册订阅者\n     * example\n     * <pre>{@code\n     *     eventBus.register(new YourEventBusSubscriber());\n     * }\n     * </pre>\n     *\n     * @param eventBus EventBus\n     * @param skeleton 业务框架\n     */\n    void registerEventBus(EventBus eventBus, BarSkeleton skeleton);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventBusSubscriber.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport java.lang.annotation.*;\n\n/**\n * 此注解不是必须的，只是为了标记，目的是结合工具可快速找到所有的订阅者配置\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface EventBusSubscriber {\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventSubscribe.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport java.lang.annotation.*;\n\n/**\n * 订阅者注解，将方法标记为事件订阅者（接收事件、处理事件），可配置线程执行器策略与执行优先级，默认是线程安全的。\n * <pre>\n *     订阅者必须且只能有一个参数，用于接收事件源。\n *     默认是线程安全的，使用的用户线程执行器。\n * </pre>\n * example\n * <pre>{@code\n *     public class YourEventBusSubscriber implements EventBusSubscriber {\n *         @EventSubscribe\n *         public void userLogin(YourEventMessage message) {\n *             log.info(\"event - 玩家[{}]登录\", message.getUserId());\n *         }\n *     }\n *\n *     @Data\n *     public class YourEventMessage implements Serializable {\n *         final long userId;\n *         public YourEventMessage(long userId) {\n *             this.userId = userId;\n *         }\n *     }\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @see DefaultSubscribeExecutorStrategy\n * @since 21\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.METHOD)\npublic @interface EventSubscribe {\n    /**\n     * 执行器策略选择\n     * <pre>\n     *     注意，只有在发送异步事件时，该配置才会生效。\n     * </pre>\n     */\n    ExecutorSelector value() default ExecutorSelector.userExecutor;\n\n    /**\n     * 订阅者的执行顺序（优先级）\n     * <pre>\n     *     值越大，执行优先级越高。\n     *\n     *     想要确保按顺序执行，订阅者需要使用相同的线程执行器。\n     *     比如可以搭配 userExecutor、simpleExecutor 等策略来使用。\n     *     这些策略通过 {@link EventBusMessage#getThreadIndex()} 来确定所使用线程执行器。\n     * </pre>\n     *\n     * @return 执行顺序\n     * @see ExecutorSelector\n     */\n    int order() default 0;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/EventTopicMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport java.util.Set;\n\n/**\n * 事件源主题\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\npublic record EventTopicMessage(Set<String> topicSet) {\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/ExecutorSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\n\n/**\n * 订阅者线程执行器选择策略。\n * <p>\n * for example\n * <pre>{@code\n *     public class YourEventBusSubscriber implements EventBusSubscriber {\n *         // 指定线程执行器来执行订阅者的逻辑\n *         @EventSubscribe(ExecutorSelector.userExecutor)\n *         public void userLogin(YourEventMessage message) {\n *             log.info(\"event - 玩家[{}]登录\", message.getUserId());\n *         }\n *     }\n *\n *     @Data\n *     public class YourEventMessage implements Serializable {\n *         final long userId;\n *         public YourEventMessage(long userId) {\n *             this.userId = userId;\n *         }\n *     }\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-01-11\n * @see EventSubscribe\n * @since 21\n */\npublic enum ExecutorSelector {\n    /**\n     * [线程安全] 在用户线程执行器中执行\n     * <pre>\n     *     该策略将使用 action 的线程执行器，可确保同一用户（userId）在消费事件和消费 action 时，\n     *     使用的是相同的线程执行器，以避免并发问题。\n     *\n     *     注意，不要做耗时 io 相关操作，避免阻塞 action 的消费。\n     * </pre>\n     *\n     * @see ExecutorRegion#getUserThreadExecutorRegion()\n     */\n    userExecutor,\n\n    /**\n     * 在虚拟线程执行器中执行\n     * <pre>\n     *     耗时相关的操作，可选择此策略\n     * </pre>\n     *\n     * @see ExecutorRegion#getUserVirtualThreadExecutorRegion()\n     */\n    userVirtualExecutor,\n\n    /**\n     * [线程安全] 在线程执行器中执行\n     * <pre>\n     *     该策略将使用 Subscriber.id 来确定线程执行器，可确保相同的订阅者方法在消费事件时，\n     *     使用的是相同的线程执行器，以避免并发问题。\n     *\n     *     注意，不要做耗时 io 相关操作，避免阻塞其他订阅者的消费。\n     *\n     *     其他补充说明：\n     *     Subscriber.id 由框架分配。该策略与 userExecutor、simpleExecutor 策略类似。\n     *     userExecutor、simpleExecutor 使用 userId 来确定线程执行器，\n     *     而 methodExecutor 则使用订阅者自身的 Subscriber.id 来确定线程执行器（你可以理解为按订阅者方法来划分）。\n     * </pre>\n     *\n     * @see ExecutorRegion#getSimpleThreadExecutorRegion()\n     */\n    methodExecutor,\n    /**\n     * [线程安全] 在线程执行器中执行\n     * <pre>\n     *     该策略与 userExecutor 类似，但使用的是独立的线程执行器（{@link ExecutorRegion#getSimpleThreadExecutorRegion() }）。\n     *     使用时，需要开发者设置 {@link EventBusMessage#setThreadIndex(long)} 的值（ 该值需要 > 0）。\n     * </pre>\n     *\n     * @see ExecutorRegion#getSimpleThreadExecutorRegion()\n     */\n    simpleExecutor,\n\n    /**\n     * 预留给开发者的\n     * <pre>\n     *     上述策略都不能满足业务的，开发者可以通过实现 {@link SubscribeExecutorStrategy} 接口来做自定义扩展\n     * </pre>\n     * example\n     * <pre>{@code\n     *         // 逻辑服添加 EventBusRunner，用于处理 EventBus 相关业务\n     *         builder.addRunner(new AbstractEventBusRunner() {\n     *             @Override\n     *             public void registerEventBus(EventBus eventBus, BarSkeleton skeleton) {\n     *                 // 你的线程执行器选择策略\n     *                 eventBus.setSubscribeExecutorStrategy(new YourSubscribeExecutorStrategy());\n     *             }\n     *         });\n     * }\n     * </pre>\n     *\n     * @see SubscribeExecutorStrategy\n     */\n    customExecutor\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/InternalAboutAny.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.collect.SetMultiMap;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.*;\nimport java.util.stream.Stream;\n\n/**\n * any tag topic\n * <p>\n * 处理 topic 时的逻辑\n * <pre>\n *     1 通过 topic 找到所有 anyTag，因为 anyTag 持有逻辑服相关的信息。\n *     2 在 anyTag 中找出一个 BrokerClient 处理该 topic，优先找当前进程中的 BrokerClient。\n *     3 得到 BrokerClient 信息后，从对应的 EventBusRegion 中得到对应的 EventBus。\n *       3.1 当 EventBus 不为 null 时，表示本地进程中有订阅者\n *       3.2 当 EventBus 为 null 时，表示只有远程的订阅者\n * </pre>\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class AnyTagBrokerClient {\n    final Map<BrokerClientId, EventBrokerClientMessage> map = new NonBlockingHashMap<>();\n    long nextRemote;\n    long nextLocal;\n    EventBrokerClientMessage[] eventRemoteBrokerClientMessages;\n    EventBrokerClientMessage[] eventLocalBrokerClientMessages;\n\n    boolean isEmpty() {\n        return this.map.isEmpty();\n    }\n\n    Stream<EventBrokerClientMessage> streamEventBrokerClientMessage() {\n        return this.map.isEmpty() ? Stream.empty() : this.map.values().stream();\n    }\n\n    void add(EventBrokerClientMessage eventBrokerClientMessage) {\n\n        String brokerClientId = eventBrokerClientMessage.getBrokerClientId();\n        BrokerClientId id = BrokerClientId.of(brokerClientId);\n        this.map.put(id, eventBrokerClientMessage);\n\n        EventBusKit.executeSafe(this::reload);\n    }\n\n    void remove(EventBrokerClientMessage eventBrokerClientMessage) {\n        String brokerClientId = eventBrokerClientMessage.getBrokerClientId();\n        BrokerClientId id = BrokerClientId.of(brokerClientId);\n        this.map.remove(id);\n\n        EventBusKit.executeSafe(this::reload);\n    }\n\n    EventBrokerClientMessage anyEventBrokerClientMessage() {\n        // 优先使用同进程的\n        int localLength = this.eventLocalBrokerClientMessages.length;\n        if (localLength > 0) {\n            if (localLength == 1) {\n                return this.eventLocalBrokerClientMessages[0];\n            }\n\n            int index = getIndex(this.nextLocal++, this.eventLocalBrokerClientMessages);\n            return this.eventLocalBrokerClientMessages[index];\n        }\n\n        int remoteLength = this.eventRemoteBrokerClientMessages.length;\n        if (remoteLength == 1) {\n            return this.eventRemoteBrokerClientMessages[0];\n        }\n\n        int index = getIndex(this.nextRemote++, this.eventRemoteBrokerClientMessages);\n        return this.eventRemoteBrokerClientMessages[index];\n    }\n\n    private int getIndex(long index, EventBrokerClientMessage[] clientMessages) {\n        return (int) (index % clientMessages.length);\n    }\n\n    static final EventBrokerClientMessage[] emptyClientMessage = new EventBrokerClientMessage[0];\n\n    private void reload() {\n\n        var localClientMessageList = new ArrayList<EventBrokerClientMessage>();\n        var remoteClientMessageList = new ArrayList<EventBrokerClientMessage>();\n\n        for (EventBrokerClientMessage clientMessage : this.map.values()) {\n            if (clientMessage.isRemote()) {\n                remoteClientMessageList.add(clientMessage);\n            } else {\n                localClientMessageList.add(clientMessage);\n            }\n        }\n\n        this.nextRemote = 0;\n        this.nextLocal = 0;\n\n        this.eventLocalBrokerClientMessages = localClientMessageList.isEmpty()\n                ? emptyClientMessage\n                : localClientMessageList.toArray(emptyClientMessage);\n\n        this.eventRemoteBrokerClientMessages = remoteClientMessageList.isEmpty()\n                ? emptyClientMessage\n                : remoteClientMessageList.toArray(emptyClientMessage);\n    }\n}\n\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class AnyTagViewData {\n    static final AnyTagViewData empty = new AnyTagViewData();\n    List<EventBrokerClientMessage> localMessages;\n    List<EventBrokerClientMessage> remoteMessages;\n\n    void add(EventBrokerClientMessage eventBrokerClientMessage) {\n        if (eventBrokerClientMessage.isRemote()) {\n            this.initRemoteMessages();\n            this.remoteMessages.add(eventBrokerClientMessage);\n        } else {\n            this.initLocalMessages();\n            this.localMessages.add(eventBrokerClientMessage);\n        }\n    }\n\n    private void initLocalMessages() {\n        if (Objects.isNull(this.localMessages)) {\n            this.localMessages = new ArrayList<>();\n        }\n    }\n\n    private void initRemoteMessages() {\n        if (Objects.isNull(this.remoteMessages)) {\n            this.remoteMessages = new ArrayList<>();\n        }\n    }\n}\n\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class AnyTagView {\n    SetMultiMap<String, AnyTagBrokerClient> topicMultiMap = SetMultiMap.of();\n\n    AnyTagViewData getAnyTagData(EventBusMessage message) {\n        String topic = message.getTopic();\n        Set<AnyTagBrokerClient> anyTagBrokerClients = this.topicMultiMap.get(topic);\n\n        if (CollKit.isEmpty(anyTagBrokerClients)) {\n            return AnyTagViewData.empty;\n        }\n\n        AnyTagViewData anyTagViewData = new AnyTagViewData();\n\n        anyTagBrokerClients.stream()\n                .map(AnyTagBrokerClient::anyEventBrokerClientMessage)\n                .forEach(anyTagViewData::add);\n\n        return anyTagViewData;\n    }\n\n    void reload(Collection<AnyTagBrokerClient> values) {\n        // 重新加载\n        SetMultiMap<String, AnyTagBrokerClient> tempMultiMap = SetMultiMap.of();\n\n        for (AnyTagBrokerClient anyTagBrokerClient : values) {\n            anyTagBrokerClient.streamEventBrokerClientMessage()\n                    .map(EventBrokerClientMessage::getTopics)\n                    .flatMap(Collection::stream)\n                    .forEach(topic -> tempMultiMap.of(topic).add(anyTagBrokerClient));\n        }\n\n        this.topicMultiMap = tempMultiMap;\n    }\n}\n\nrecord BrokerClientTag(String tag) {\n    static final Map<String, BrokerClientTag> map = new NonBlockingHashMap<>();\n\n    static BrokerClientTag of(String tag) {\n        BrokerClientTag brokerClientTag = map.get(tag);\n\n        if (Objects.isNull(brokerClientTag)) {\n            var theTag = new BrokerClientTag(tag);\n            return MoreKit.firstNonNull(map.putIfAbsent(tag, theTag), theTag);\n        }\n\n        return brokerClientTag;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof BrokerClientTag that)) {\n            return false;\n        }\n\n        return tag.equals(that.tag);\n    }\n\n    @Override\n    public int hashCode() {\n        return tag.hashCode();\n    }\n}\n\nrecord BrokerClientId(String id) {\n    static final Map<String, BrokerClientId> map = new NonBlockingHashMap<>();\n\n    static BrokerClientId of(String id) {\n        BrokerClientId brokerClientId = map.get(id);\n\n        if (Objects.isNull(brokerClientId)) {\n            var theId = new BrokerClientId(id);\n            return MoreKit.firstNonNull(map.putIfAbsent(id, theId), theId);\n        }\n\n        return brokerClientId;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof BrokerClientId that)) {\n            return false;\n        }\n\n        return id.equals(that.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return id.hashCode();\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/InternalAboutEventBus.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport com.esotericsoftware.reflectasm.ConstructorAccess;\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport com.iohao.game.action.skeleton.core.IoGameCommonCoreConfig;\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.collect.ListMultiMap;\nimport com.iohao.game.common.kit.collect.SetMultiMap;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.common.kit.trace.TraceKit;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\nimport org.jctools.maps.NonBlockingHashSet;\nimport org.slf4j.MDC;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n@Slf4j\nfinal class DefaultEventBusListener implements EventBusListener {\n    @Override\n    public void invokeException(Throwable e, Object eventSource, EventBusMessage eventBusMessage) {\n        log.error(e.getMessage(), e);\n    }\n\n    @Override\n    public void emptySubscribe(EventBusMessage eventBusMessage, EventBus eventBus) {\n        Class<?> clazz = eventBusMessage.getTopicClass();\n        String simpleName = eventBusMessage.getTopic();\n        log.warn(\"事件源[{}]没有配置订阅者 {}\", clazz.getSimpleName(), simpleName);\n    }\n\n    static DefaultEventBusListener me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DefaultEventBusListener ME = new DefaultEventBusListener();\n    }\n}\n\nfinal class DefaultEventBusMessageCreator implements EventBusMessageCreator {\n    @Override\n    public EventBusMessage create(Object eventSource) {\n\n        EventBusMessage eventBusMessage = new EventBusMessage();\n        eventBusMessage.setEventSource(eventSource);\n\n        // traceId\n        String traceId = MDC.get(TraceKit.traceName);\n        eventBusMessage.setTraceId(traceId);\n\n        return eventBusMessage;\n    }\n\n    static DefaultEventBusMessageCreator me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DefaultEventBusMessageCreator ME = new DefaultEventBusMessageCreator();\n    }\n}\n\nfinal class DefaultSubscribeExecutorStrategy implements SubscribeExecutorStrategy {\n    static final AtomicLong threadIndexNo = new AtomicLong();\n\n    @Override\n    public ThreadExecutor select(Subscriber subscriber, EventBusMessage eventBusMessage, ExecutorRegion executorRegion) {\n\n        ExecutorSelector executorSelect = subscriber.getExecutorSelect();\n\n        // 虚拟线程中执行\n        if (executorSelect == ExecutorSelector.userVirtualExecutor) {\n            long threadIndex = getThreadIndex(eventBusMessage);\n            return executorRegion.getUserVirtualThreadExecutor(threadIndex);\n        }\n\n        // [线程安全] 用户线程中执行\n        if (executorSelect == ExecutorSelector.userExecutor) {\n            long threadIndex = getThreadIndex(eventBusMessage);\n            return executorRegion.getUserThreadExecutor(threadIndex);\n        }\n\n        // [线程安全] 相同的订阅者使用同一个线程执行器\n        if (executorSelect == ExecutorSelector.methodExecutor) {\n            long threadIndex = subscriber.id;\n            return executorRegion.getSimpleThreadExecutor(threadIndex);\n        }\n\n        long threadIndex = getThreadIndex(eventBusMessage);\n        return executorRegion.getSimpleThreadExecutor(threadIndex);\n    }\n\n    long getThreadIndex(EventBusMessage eventBusMessage) {\n        long userId = eventBusMessage.getThreadIndex();\n\n        if (userId != 0) {\n            return userId;\n        }\n\n        return threadIndexNo.getAndIncrement();\n    }\n\n    static DefaultSubscribeExecutorStrategy me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DefaultSubscribeExecutorStrategy ME = new DefaultSubscribeExecutorStrategy();\n    }\n}\n\nrecord DefaultSubscriberInvoke(Subscriber subscriber) implements SubscriberInvoke {\n    @Override\n    public void invoke(EventBusMessage eventBusMessage) {\n        var eventSource = eventBusMessage.getEventSource();\n        var traceId = eventBusMessage.getTraceId();\n\n        final boolean test = Objects.isNull(traceId) || Objects.nonNull(MDC.get(TraceKit.traceName));\n        if (test) {\n            this.invoke(eventSource);\n            return;\n        }\n\n        try {\n            // traceId\n            MDC.put(TraceKit.traceName, traceId);\n            this.invoke(eventSource);\n        } finally {\n            MDC.clear();\n        }\n    }\n\n    void invoke(Object param) {\n        Object target = this.subscriber.getTarget();\n        int methodIndex = this.subscriber.getMethodIndex();\n\n        MethodAccess methodAccess = this.subscriber.getMethodAccess();\n        methodAccess.invoke(target, methodIndex, param);\n    }\n}\n\nfinal class DefaultSubscriberInvokeCreator implements SubscriberInvokeCreator {\n\n    @Override\n    public SubscriberInvoke create(Subscriber subscriber) {\n        return new DefaultSubscriberInvoke(subscriber);\n    }\n\n    static DefaultSubscriberInvokeCreator me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DefaultSubscriberInvokeCreator ME = new DefaultSubscriberInvokeCreator();\n    }\n}\n\n@UtilityClass\nclass EventBusKit {\n    void executeSafe(Runnable runnable) {\n        ExecutorRegionKit.getSimpleThreadExecutor(1).executeTry(runnable);\n    }\n\n    void sort(List<Subscriber> subscribers) {\n        // order 排序\n        subscribers.sort((o1, o2) -> o2.order - o1.order);\n    }\n}\n\n@UtilityClass\nfinal class EventBusLocalRegion {\n    /**\n     * EventBus map\n     * <pre>\n     *     key : id\n     * </pre>\n     */\n    final Map<String, EventBus> eventBusMap = new NonBlockingHashMap<>();\n    /**\n     * 事件源与订阅者的映射\n     * <pre>\n     *     key : 事件源\n     *     value : 订阅者集合。（当前进程内的所有订阅者）\n     * </pre>\n     */\n    final ListMultiMap<Class<?>, Subscriber> subscriberListMap = ListMultiMap.of();\n\n    public EventBus getEventBus(String brokerClientId) {\n        return eventBusMap.get(brokerClientId);\n    }\n\n    boolean hasLocalNeighbor() {\n        // 当只有一个 eventBus 时，通常是自己，就也是当前 eventBus;\n        return eventBusMap.size() > 1;\n    }\n\n    /**\n     * 根据事件消息，获取当前进程所有的订阅者\n     *\n     * @param eventBusMessage 事件消息\n     * @return 当前进程所有的订阅者\n     */\n    List<Subscriber> listLocalSubscriber(EventBusMessage eventBusMessage) {\n        Class<?> eventSourceClazz = eventBusMessage.getTopicClass();\n        return subscriberListMap.get(eventSourceClazz);\n    }\n\n    Stream<EventBus> streamEventBus() {\n        return eventBusMap.values().stream();\n    }\n\n    void addLocal(EventBus eventBus) {\n        eventBusMap.put(eventBus.getId(), eventBus);\n\n        EventBusKit.executeSafe(EventBusLocalRegion::resetLocalSubscriber);\n    }\n\n    private void resetLocalSubscriber() {\n\n        ListMultiMap<Class<?>, Subscriber> tempMultiMap = ListMultiMap.of();\n        for (EventBus eventBus : eventBusMap.values()) {\n            if (eventBus instanceof DefaultEventBus defaultEventBus) {\n                SubscriberRegistry subscriberRegistry = defaultEventBus.subscriberRegistry;\n                var multiMap = subscriberRegistry.subscriberMultiMap;\n\n                if (multiMap.isEmpty()) {\n                    continue;\n                }\n\n                for (var entry : multiMap.entrySet()) {\n                    Class<?> key = entry.getKey();\n                    tempMultiMap.of(key).addAll(entry.getValue());\n                }\n            }\n        }\n\n        subscriberListMap.clear();\n\n        for (Map.Entry<Class<?>, List<Subscriber>> entry : tempMultiMap.entrySet()) {\n            var subscribers = entry.getValue();\n\n            EventBusKit.sort(subscribers);\n\n            subscriberListMap.of(entry.getKey()).addAll(subscribers);\n        }\n    }\n}\n\n@UtilityClass\nfinal class EventBusAnyTagRegion {\n    /**\n     * key : EventBrokerClientMessage tag\n     */\n    Map<BrokerClientTag, AnyTagBrokerClient> map = new NonBlockingHashMap<>();\n\n    AnyTagView anyTagView = new AnyTagView();\n\n    AnyTagViewData getAnyTagData(EventBusMessage message) {\n        return anyTagView.getAnyTagData(message);\n    }\n\n    void add(EventBrokerClientMessage eventBrokerClientMessage) {\n        // 将当前进程和远程的 EventBrokerClientMessage 添加到对应的 tag 中\n        BrokerClientTag tag = BrokerClientTag.of(eventBrokerClientMessage.tag);\n\n        AnyTagBrokerClient anyTagBrokerClient = getAnyTagBrokerClient(tag);\n        anyTagBrokerClient.add(eventBrokerClientMessage);\n\n        reload();\n    }\n\n    void remove(EventBrokerClientMessage eventBrokerClientMessage) {\n        BrokerClientTag tag = BrokerClientTag.of(eventBrokerClientMessage.tag);\n\n        AnyTagBrokerClient anyTagBrokerClient = getAnyTagBrokerClient(tag);\n        anyTagBrokerClient.remove(eventBrokerClientMessage);\n\n        // 如果 tag 没有任何数据时，从 map 中移除\n        if (anyTagBrokerClient.isEmpty()) {\n            map.remove(tag);\n        }\n\n        reload();\n    }\n\n    private AnyTagBrokerClient getAnyTagBrokerClient(BrokerClientTag tag) {\n        AnyTagBrokerClient anyTagBrokerClient = map.get(tag);\n\n        if (Objects.isNull(anyTagBrokerClient)) {\n            AnyTagBrokerClient region = new AnyTagBrokerClient();\n            return MoreKit.firstNonNull(map.putIfAbsent(tag, region), region);\n        }\n\n        return anyTagBrokerClient;\n    }\n\n    private void reload() {\n        // 重新加载视图\n        EventBusKit.executeSafe(() -> anyTagView.reload(map.values()));\n    }\n}\n\n@UtilityClass\nfinal class EventBusRemoteRegion {\n    /**\n     * 其他进程的订阅者\n     * <pre>\n     *     key : eventSource class name\n     *     value : across progress id\n     * </pre>\n     */\n    final SetMultiMap<String, EventBrokerClientMessage> remoteTopicMultiMap = SetMultiMap.of();\n\n    /**\n     * 其他进程逻辑服的信息\n     * <pre>\n     *     key : EventBrokerClientMessage id\n     * </pre>\n     */\n    final Map<String, EventBrokerClientMessage> eventBrokerClientMessageMap = new NonBlockingHashMap<>();\n\n    /**\n     * 加载其他进程的订阅者主题\n     *\n     * @param eventBrokerClientMessage 其他进程的逻辑服信息\n     */\n    public void loadRemoteEventTopic(EventBrokerClientMessage eventBrokerClientMessage) {\n        Collection<String> topics = eventBrokerClientMessage.getTopics();\n        topics.forEach(topic -> remoteTopicMultiMap.put(topic, eventBrokerClientMessage));\n        eventBrokerClientMessageMap.put(eventBrokerClientMessage.brokerClientId, eventBrokerClientMessage);\n    }\n\n    public void unloadRemoteTopic(EventBrokerClientMessage eventBrokerClientMessage) {\n        Collection<String> topics = eventBrokerClientMessage.getTopics();\n        for (String topic : topics) {\n            Set<EventBrokerClientMessage> eventBrokerClientMessages = remoteTopicMultiMap.get(topic);\n            eventBrokerClientMessages.remove(eventBrokerClientMessage);\n        }\n\n        eventBrokerClientMessageMap.remove(eventBrokerClientMessage.brokerClientId);\n    }\n\n    Set<EventBrokerClientMessage> listRemoteEventBrokerClientMessage(EventBusMessage eventBusMessage) {\n        String name = eventBusMessage.getTopic();\n        return remoteTopicMultiMap.get(name);\n    }\n}\n\n/**\n * 订阅者注册\n *\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@Slf4j\n@FieldDefaults(level = AccessLevel.PACKAGE)\nfinal class SubscriberRegistry {\n    static final AtomicLong subscriberId = new AtomicLong();\n    final ListMultiMap<Class<?>, Subscriber> subscriberMultiMap = ListMultiMap.of();\n    final Set<Class<?>> eventBusSubscriberSet = new NonBlockingHashSet<>();\n\n    EventBus eventBus;\n\n    void register(Object eventBusSubscriber, SubscriberInvokeCreator subscriberInvokeCreator) {\n\n        Class<?> clazz = eventBusSubscriber.getClass();\n\n        if (!eventBusSubscriberSet.add(clazz)) {\n            ThrowKit.ofRuntimeException(\"Already exists : \" + clazz);\n        }\n\n        // 方法访问器: 获取类中自己定义的方法\n        var methodAccess = MethodAccess.get(clazz);\n        var constructorAccess = ConstructorAccess.get(clazz);\n\n        streamMethod(clazz).forEach(method -> {\n            var parameter = method.getParameters()[0];\n\n            // 方法名\n            String methodName = method.getName();\n            // 方法下标\n            var parameterClass = parameter.getType();\n            int methodIndex = methodAccess.getIndex(methodName, parameterClass);\n\n            // 订阅者注解相关\n            var annotation = method.getAnnotation(EventSubscribe.class);\n            var executorSelector = annotation.value();\n            int order = Math.abs(annotation.order());\n\n            Subscriber subscriber = new Subscriber(subscriberId.getAndIncrement())\n                    .setMethodAccess(methodAccess)\n                    .setConstructorAccess(constructorAccess)\n                    .setMethodName(methodName)\n                    .setMethod(method)\n                    .setMethodIndex(methodIndex)\n                    .setTargetClazz(clazz)\n                    .setTarget(eventBusSubscriber)\n                    .setParameterName(parameter.getName())\n                    .setParameterClass(parameterClass)\n                    .setOrder(order)\n                    .setEventBus(eventBus)\n                    .setExecutorSelect(executorSelector);\n\n            SubscriberInvoke subscriberInvoke = subscriberInvokeCreator.create(subscriber);\n            subscriber.setSubscriberInvoke(subscriberInvoke);\n\n            this.subscriberMultiMap.put(parameterClass, subscriber);\n        });\n\n        this.subscriberMultiMap.asMap().values().forEach(EventBusKit::sort);\n    }\n\n    Collection<Class<?>> listEventSourceClass() {\n        return this.subscriberMultiMap.keySet();\n    }\n\n    Collection<Subscriber> listSubscriber(EventBusMessage eventBusMessage) {\n\n        Class<?> methodParamClazz = eventBusMessage.getTopicClass();\n\n        return this.subscriberMultiMap.containsKey(methodParamClazz)\n                ? this.subscriberMultiMap.get(methodParamClazz)\n                : Collections.emptyList();\n    }\n\n    private Stream<Method> streamMethod(Class<?> clazz) {\n        return Arrays.stream(clazz.getDeclaredMethods())\n                // 添加了订阅者注解的方法\n                .filter(method -> Objects.nonNull(method.getAnnotation(EventSubscribe.class)))\n                .filter(method -> {\n                    // 访问权限必须是 public 的\n                    boolean isPublic = Modifier.isPublic(method.getModifiers());\n                    var notStatic = !Modifier.isStatic(method.getModifiers());\n                    var onceParam = method.getParameters().length == 1;\n                    var isVoid = method.getReturnType() == Void.TYPE;\n                    /*\n                     * 访问权限必须是 public 的\n                     * 不能是静态方法的\n                     * 方法必须有一个参数\n                     * 方法返回值必须为 void\n                     */\n                    var result = isPublic && notStatic && onceParam && isVoid;\n\n                    if (!result) {\n                        log.warn(\"不是标准的订阅方法 {}\", method);\n                    }\n\n                    return result;\n                })\n                // 订阅者必须且只能有一个参数，用于接收事件源\n                .filter(method -> method.getParameters().length == 1)\n                ;\n    }\n}\n\nenum EventBusStatus {\n    register,\n    run\n}\n\n@Slf4j\n@Setter\n@Getter\n@FieldDefaults(level = AccessLevel.PACKAGE)\nfinal class DefaultEventBus implements EventBus {\n    /** 订阅者管理 */\n    final SubscriberRegistry subscriberRegistry = new SubscriberRegistry();\n\n    final String id;\n    SubscribeExecutorStrategy subscribeExecutorStrategy;\n    SubscriberInvokeCreator subscriberInvokeCreator;\n    EventBusMessageCreator eventBusMessageCreator;\n    EventBusListener eventBusListener;\n\n    /** 对应逻辑服的相关信息 */\n    EventBrokerClientMessage eventBrokerClientMessage;\n    /** 逻辑服 */\n    BrokerClientContext brokerClientContext;\n    /** 与业务框架所关联的线程执行器管理域 */\n    ExecutorRegion executorRegion;\n\n    EventBusStatus status = EventBusStatus.register;\n\n    DefaultEventBus(String id) {\n        this.id = Objects.requireNonNull(id);\n    }\n\n    public void setBrokerClientContext(BrokerClientContext brokerClientContext) {\n        this.brokerClientContext = brokerClientContext;\n    }\n\n    @Override\n    public void register(Object eventBusSubscriber) {\n\n        if (status != EventBusStatus.register) {\n            // 运行中不允许注册订阅者，请在 EventRunner.registerEventBus 方法中注册。\n            ThrowKit.ofRuntimeException(\"Subscriber registration is not allowed during running. Please register in EventRunner.registerEventBus method.\");\n        }\n\n        // 注册\n        this.subscriberRegistry.eventBus = this;\n        this.subscriberRegistry.register(eventBusSubscriber, this.subscriberInvokeCreator);\n    }\n\n    @Override\n    public EventBusMessage createEventBusMessage(Object eventSource) {\n        return this.eventBusMessageCreator.create(eventSource);\n    }\n\n    @Override\n    public Set<String> listTopic() {\n        // 当前 eventBus 订阅的所有事件源主题\n        return this.subscriberRegistry\n                .listEventSourceClass()\n                .stream()\n                .map(Class::getName)\n                .collect(Collectors.toSet());\n    }\n\n    @Override\n    public void fire(EventBusMessage eventBusMessage) {\n        // 给当前进程所有逻辑服的订阅者发送事件消息\n        this.fireLocal(eventBusMessage);\n        // 给其他进程的订阅者发送事件\n        this.fireRemote(eventBusMessage);\n\n        if (eventBusMessage.emptyFireType()) {\n            this.eventBusListener.emptySubscribe(eventBusMessage, this);\n        }\n    }\n\n    @Override\n    public void fireSync(EventBusMessage eventBusMessage) {\n        // 给当前进程所有逻辑服的订阅者发送事件消息\n        this.fireLocalSync(eventBusMessage);\n        // 给其他进程的订阅者发送事件\n        this.fireRemote(eventBusMessage);\n\n        if (eventBusMessage.emptyFireType()) {\n            this.eventBusListener.emptySubscribe(eventBusMessage, this);\n        }\n    }\n\n    @Override\n    public void fire(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fire(eventBusMessage);\n    }\n\n    @Override\n    public void fireSync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireSync(eventBusMessage);\n    }\n\n    @Override\n    public void fireLocal(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireLocal(eventBusMessage);\n    }\n\n    @Override\n    public void fireLocal(EventBusMessage eventBusMessage) {\n        this.fireLocal(eventBusMessage, true);\n    }\n\n    @Override\n    public void fireLocalSync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireLocalSync(eventBusMessage);\n    }\n\n    @Override\n    public void fireLocalSync(EventBusMessage eventBusMessage) {\n        this.fireLocal(eventBusMessage, false);\n    }\n\n    private void fireLocal(EventBusMessage eventBusMessage, boolean async) {\n        List<Subscriber> subscribers = EventBusRegion.listLocalSubscriber(eventBusMessage);\n        if (CollKit.isEmpty(subscribers)) {\n            return;\n        }\n\n        eventBusMessage.addFireType(EventBusFireType.fireLocal);\n\n        // 发送事件\n        this.invokeSubscriber(eventBusMessage, async, subscribers);\n    }\n\n    void fireRemote(EventBusMessage eventBusMessage) {\n        var messages = EventBusRegion.listRemoteEventBrokerClientMessage(eventBusMessage);\n\n        this.fireRemote(eventBusMessage, messages);\n    }\n\n    void fireRemote(EventBusMessage eventBusMessage, Collection<EventBrokerClientMessage> messages) {\n        if (CollKit.isEmpty(messages)) {\n            return;\n        }\n\n        // 如果其他进程中存在当前事件源的订阅者，将事件源发布到其他进程中\n        eventBusMessage.setEventBrokerClientMessages(messages);\n        eventBusMessage.addFireType(EventBusFireType.fireRemote);\n\n        this.extractedPrint(eventBusMessage);\n\n        try {\n            this.brokerClientContext.oneway(eventBusMessage);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public void fireMe(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireMe(eventBusMessage);\n    }\n\n    @Override\n    public void fireMe(EventBusMessage eventBusMessage) {\n        this.fireMe(eventBusMessage, true);\n    }\n\n    @Override\n    public void fireMeSync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireMeSync(eventBusMessage);\n    }\n\n    @Override\n    public void fireMeSync(EventBusMessage eventBusMessage) {\n        this.fireMe(eventBusMessage, false);\n    }\n\n    private void fireMe(EventBusMessage eventBusMessage, boolean async) {\n        Collection<Subscriber> subscribers = this.listSubscriber(eventBusMessage);\n\n        if (CollKit.isEmpty(subscribers)) {\n            return;\n        }\n\n        eventBusMessage.addFireType(EventBusFireType.fireMe);\n\n        this.invokeSubscriber(eventBusMessage, async, subscribers);\n    }\n\n    @Override\n    public void fireAny(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireAny(eventBusMessage);\n    }\n\n    @Override\n    public void fireAny(EventBusMessage eventBusMessage) {\n        AnyTagViewData anyTagViewData = EventBusAnyTagRegion.getAnyTagData(eventBusMessage);\n\n        List<EventBrokerClientMessage> messages = anyTagViewData.getLocalMessages();\n        this.fireAny(eventBusMessage, messages, true);\n\n        List<EventBrokerClientMessage> remoteMessages = anyTagViewData.getRemoteMessages();\n        this.fireRemote(eventBusMessage, remoteMessages);\n\n        if (eventBusMessage.emptyFireType()) {\n            this.eventBusListener.emptySubscribe(eventBusMessage, this);\n        }\n    }\n\n    @Override\n    public void fireAnySync(Object eventSource) {\n        EventBusMessage eventBusMessage = this.createEventBusMessage(eventSource);\n        this.fireAnySync(eventBusMessage);\n    }\n\n    @Override\n    public void fireAnySync(EventBusMessage eventBusMessage) {\n        AnyTagViewData anyTagViewData = EventBusAnyTagRegion.getAnyTagData(eventBusMessage);\n\n        List<EventBrokerClientMessage> messages = anyTagViewData.getLocalMessages();\n        this.fireAny(eventBusMessage, messages, false);\n\n        List<EventBrokerClientMessage> remoteMessages = anyTagViewData.getRemoteMessages();\n        this.fireRemote(eventBusMessage, remoteMessages);\n\n        if (eventBusMessage.emptyFireType()) {\n            this.eventBusListener.emptySubscribe(eventBusMessage, this);\n        }\n    }\n\n    void fireAny(EventBusMessage eventBusMessage, List<EventBrokerClientMessage> list, boolean async) {\n\n        if (CollKit.isEmpty(list)) {\n            return;\n        }\n\n        for (EventBrokerClientMessage brokerClientMessage : list) {\n            EventBus eventBus = EventBusRegion.getEventBus(brokerClientMessage.brokerClientId);\n            if (eventBus instanceof DefaultEventBus defaultEventBus) {\n                defaultEventBus.fireMe(eventBusMessage, async);\n            }\n        }\n    }\n\n    @Override\n    public void fireLocalNeighbor(EventBusMessage eventBusMessage) {\n        this.fireLocalNeighbor(eventBusMessage, true);\n    }\n\n    @Override\n    public void fireLocalNeighborSync(EventBusMessage eventBusMessage) {\n        this.fireLocalNeighbor(eventBusMessage, false);\n    }\n\n    /**\n     * 给当前进程其他逻辑服的订阅者发送事件消息，不包括当前 EventBus。\n     *\n     * @param eventBusMessage 事件消息\n     * @param async           true 表示异步执行\n     */\n    private void fireLocalNeighbor(EventBusMessage eventBusMessage, boolean async) {\n        if (!EventBusRegion.hasLocalNeighbor()) {\n            // 当前进程仅有一个逻辑服\n            return;\n        }\n\n        var subscribers = EventBusRegion.streamLocalEventBus()\n                // 排除自己（当前 EventBus）\n                .filter(eventBus -> !Objects.equals(this, eventBus))\n                // 得到所有的订阅者\n                .flatMap(eventBus -> eventBus.listSubscriber(eventBusMessage).stream())\n                .toList();\n\n        if (CollKit.isEmpty(subscribers)) {\n            return;\n        }\n\n        eventBusMessage.addFireType(EventBusFireType.fireLocalNeighbor);\n\n        // 发送事件\n        this.invokeSubscriber(eventBusMessage, async, subscribers);\n    }\n\n    private void extractedPrint(EventBusMessage eventBusMessage) {\n        if (IoGameCommonCoreConfig.eventBusLog) {\n            log.info(\"###### 触发远程逻辑服的订阅者 - {} -  : {}\", this.eventBrokerClientMessage.getAppName(), eventBusMessage);\n            for (EventBrokerClientMessage eventBrokerClientMessage : eventBusMessage.getEventBrokerClientMessages()) {\n                log.info(\"远程逻辑服 : {}\", eventBrokerClientMessage.getAppName());\n            }\n\n            IoGameBanner.printLine();\n        }\n    }\n\n    private void invokeSubscriber(EventBusMessage eventBusMessage, boolean async, Collection<Subscriber> subscribers) {\n        if (async) {\n            // 异步执行\n            for (Subscriber subscriber : subscribers) {\n                EventBus eventBus = subscriber.getEventBus();\n                ExecutorRegion executorRegion = eventBus.getExecutorRegion();\n\n                // 根据策略得到对应的执行器\n                SubscribeExecutorStrategy executorStrategy = eventBus.getSubscribeExecutorStrategy();\n                ThreadExecutor threadExecutor = executorStrategy.select(subscriber, eventBusMessage, executorRegion);\n                threadExecutor.execute(() -> this.invoke(subscriber, eventBusMessage));\n            }\n        } else {\n            // 同步执行\n            for (Subscriber subscriber : subscribers) {\n                this.invoke(subscriber, eventBusMessage);\n            }\n        }\n    }\n\n    private void invoke(Subscriber subscriber, EventBusMessage eventBusMessage) {\n        try {\n            SubscriberInvoke subscriberInvoke = subscriber.getSubscriberInvoke();\n            subscriberInvoke.invoke(eventBusMessage);\n        } catch (Throwable e) {\n            EventBus eventBus = subscriber.getEventBus();\n            EventBusListener eventBusListener = eventBus.getEventBusListener();\n            eventBusListener.invokeException(e, eventBusMessage.getEventSource(), eventBusMessage);\n        }\n    }\n\n    @Override\n    public Collection<Subscriber> listSubscriber(EventBusMessage eventBusMessage) {\n        return this.subscriberRegistry.listSubscriber(eventBusMessage);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof DefaultEventBus that)) {\n            return false;\n        }\n\n        return this.id.equals(that.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return id.hashCode();\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/SubscribeExecutorStrategy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\n\n/**\n * 订阅者线程执行器选择策略\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\npublic interface SubscribeExecutorStrategy {\n    /**\n     * 得到对应的线程执行器\n     *\n     * @param subscriber      订阅者\n     * @param eventBusMessage 事件消息\n     * @param executorRegion  与业务框架所关联的线程执行器管理域\n     * @return 线程执行器\n     */\n    ThreadExecutor select(Subscriber subscriber, EventBusMessage eventBusMessage, ExecutorRegion executorRegion);\n\n    static SubscribeExecutorStrategy defaultInstance() {\n        return DefaultSubscribeExecutorStrategy.me();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/Subscriber.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport com.esotericsoftware.reflectasm.ConstructorAccess;\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\n\nimport java.lang.reflect.Method;\n\n/**\n * 订阅者\n * <pre>\n *     通常由添加了 {@link EventSubscribe} 注解的方法转换而来\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @see EventSubscribe\n * @since 21\n */\n@Accessors(chain = true)\n@Setter(AccessLevel.PACKAGE)\npublic final class Subscriber {\n    @Getter\n    final long id;\n    /** 方法访问器 */\n    @Getter\n    MethodAccess methodAccess;\n    /** 类访问器 */\n    ConstructorAccess<?> constructorAccess;\n    /** 方法名 */\n    String methodName;\n    /** EventSubscribe class */\n    Class<?> targetClazz;\n    @Getter\n    Object target;\n    /** 方法对象 */\n    Method method;\n    /** 方法下标 (配合 MethodAccess 使用) */\n    @Getter\n    int methodIndex;\n    /** 方法参数名 */\n    String parameterName;\n    /** 方法参数类型 */\n    Class<?> parameterClass;\n    /**\n     * 执行的顺序（类似优先级）\n     * <pre>\n     *     想要确保按顺序执行，需要确定使用的是相同的线程执行器\n     * </pre>\n     *\n     * @see ExecutorSelector\n     */\n    int order;\n    /** 执行器选择策略 */\n    @Getter\n    ExecutorSelector executorSelect;\n    /** 订阅者执行 */\n    @Getter\n    SubscriberInvoke subscriberInvoke;\n    /** EventBus */\n    @Getter(AccessLevel.PACKAGE)\n    EventBus eventBus;\n\n    Subscriber(long id) {\n        this.id = id;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/SubscriberInvoke.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\n/**\n * 订阅者执行\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\npublic interface SubscriberInvoke {\n    /**\n     * 执行订阅者方法\n     *\n     * @param eventBusMessage 事件消息\n     * @see EventSubscribe\n     */\n    void invoke(EventBusMessage eventBusMessage);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/SubscriberInvokeCreator.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\n/**\n * create SubscriberInvoke\n *\n * @author 渔民小镇\n * @date 2023-12-24\n * @since 21\n */\npublic interface SubscriberInvokeCreator {\n\n    /**\n     * 创建订阅者 invoke\n     *\n     * @param subscriber 订阅者\n     * @return 订阅者执行\n     */\n    SubscriberInvoke create(Subscriber subscriber);\n\n    static SubscriberInvokeCreator defaultInstance() {\n        return DefaultSubscriberInvokeCreator.me();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/eventbus/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * <a href=\"https://iohao.github.io/game/docs/communication/event_bus\">分布式事件总线相关文档</a>，分布式事件总线与 Guava EventBus、Redis 发布订阅、MQ 等产品类似。\n * <p>\n * 使用场景与简介\n * <pre>\n *     如果使用 Redis、MQ ...等中间件，需要开发者额外的安装这些中间件，并支付所占用机器的费用；使用 Guava EventBus 则只能在当前进程中通信，无法实现跨进程。\n *     而 ioGame 提供的分布式事件总线，拥有上述两者的优点。此外，还可以有效的帮助企业节省云上 Redis、 MQ 这部分的支出。\n *     事件发布后，除了当前进程所有的订阅者能接收到，远程的订阅者也能接收到（支持跨机器、跨进程、跨不同类型的多个逻辑服）。可以代替 redis pub sub 、 MQ ，并且具备全链路调用日志跟踪，这点是中间件产品做不到的。\n * </pre>\n * 特点\n * <pre>\n *     ioGame 分布式事件总线，特点：\n *         1. 使用方式与 Guava EventBus 类似\n *         2. 具备全链路调用日志跟踪。（这点是中间件产品做不到的）\n *         3. 支持跨多个机器、多个进程通信\n *         4. 支持与多种不同类型的多个逻辑服通信\n *         5. 纯 javaSE，不依赖其他服务，耦合性低。（不需要安装任何中间件）\n *         6. 事件源和事件监听器之间通过事件进行通信，从而实现了模块之间的解耦\n *         7. 当没有任何远程订阅者时，将不会触发网络请求。（这点是中间件产品做不到的）\n * </pre>\n * 概念\n * <pre>\n *     ioGame 提供的分布式事件总线在使用上是简单的，只有 3 个概念，分别是：\n *         1. 事件源：事件源由开发者定义。\n *         2. 订阅者：订阅者由开发者定义。\n *         3. 发布事件\n * </pre>\n * 发布事件\n * <pre>\n *     在发布事件时，可控制同步和异步发送。\n *\n *     这里的【同步】指的是：发布事件时，相关订阅者执行完成后，主逻辑才会继续往下走。\n *     这里的【异步】指的是：发布事件时，主逻辑不会阻塞，相关订阅者会在其他线程中执行。\n *\n *     无论是同步或者是异步，相关订阅者在执行逻辑服时，默认是线程安全的；这是因为订阅者 {@link com.iohao.game.action.skeleton.eventbus.EventSubscribe} 默认使用的是用户线程执行器。\n * </pre>\n * 注意事项\n * <pre>\n *     如果你的逻辑服没有任何订阅者，只是发送事件，也是需要配置 EventBusRunner 的，这是因为事件总线是按需要加载的功能。\n *     ioGame 功能特性很多，但不是每个项目都需要这些功能。按需加载有很多好处，比如 email 逻辑服后续的业务不想参与任何订阅了，那么把这个 Runner 注释掉就行了。其他代码不用改，这样也不会占用资源。\n *     所以，需要将 EventBusRunner 添加到业务框架后，分布式事件总线相关功能才会生效。\n * </pre>\n * <p>\n * for example，下面示例展示了分布式事件总线的开启、注册订阅者、定义事件源、发布事件\n * <pre>{@code\n * // 通过业务框架的 addRunner 方法来扩展分布式事件总线相关内容 （Runner 扩展机制），我们将 UserLoginEventMessage、EmailEventBusSubscriber 注册到 EventBus 中。\n * public class MyLogicStartup extends AbstractBrokerClientStartup {\n *     ... ...省略部分代码\n *\n *     @Override\n *     public BarSkeleton createBarSkeleton() {\n *         // 业务框架构建器\n *         var builder = ...\n *\n *         // 开启分布式事件总线。逻辑服添加 EventBusRunner，用于处理 EventBus 相关业务\n *         builder.addRunner(new EventBusRunner() {\n *             @Override\n *             public void registerEventBus(EventBus eventBus, BarSkeleton skeleton) {\n *                 // 注册订阅者\n *                 eventBus.register(new EmailEventBusSubscriber());\n *                 eventBus.register(new UserEventBusSubscriber());\n *             }\n *         });\n *\n *         return builder.build();\n *     }\n * }\n *\n * // 事件源由开发者定义。事件源是业务数据载体等，其主要目的是用于业务数据的传输。\n * @Data\n * public class UserLoginEventMessage implements Serializable {\n *     final long userId;\n *\n *     public UserLoginEventMessage(long userId) {\n *         this.userId = userId;\n *     }\n * }\n *\n * // 订阅者由开发者定义。\n * // 我们在 EmailEventBusSubscriber、UserEventBusSubscriber 类中，分别提供了 UserLoginEventMessage 事件源的订阅者 mail、userLogin。\n * // 当有 UserLoginEventMessage 相关的事件触发，订阅者都能接收到事件。别忘记，当前介绍的是分布式事件总线；所以，即使订阅者在不同的进程中，也能接收到事件。\n * // 另外，值得称赞的是，如果没有任何远程订阅者，将不会触发网络请求。简单的说，事件发布后，当其他进程（其他机器）没有相关订阅者时，只会在内存中传递事件给当前进程的相关订阅者。所以，可以将该通讯方式当作 guava EventBus 来使用。\n * @EventBusSubscriber\n * public class EmailEventBusSubscriber {\n *     @EventSubscribe\n *     public void mail(UserLoginEventMessage message) {\n *         long userId = message.getUserId();\n *         log.info(\"event - 玩家[{}]登录，发放 email 奖励\", userId);\n *     }\n * }\n *\n * @EventBusSubscriber\n * public class UserEventBusSubscriber {\n *     @EventSubscribe\n *     public void userLogin(UserLoginEventMessage message) {\n *         log.info(\"event - 玩家[{}]登录，记录登录时间\", message.getUserId());\n *     }\n * }\n *\n * // 发布事件\n * @ActionController(UserCmd.cmd)\n * public class UserAction {\n *     @ActionMethod(UserCmd.fireEvent)\n *     public void fireEventUser(FlowContext flowContext)  {\n *         long userId = flowContext.getUserId();\n *         // 事件源\n *         var message = new UserLoginEventMessage(userId);\n *         // 发布事件\n *         flowContext.fire(message);\n *         // 事件发布后，会被 UserEventBusSubscriber、EmailEventBusSubscriber 接收。\n *     }\n * }\n *\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2024-06-06\n * @see com.iohao.game.action.skeleton.core.runner.Runner\n * @see com.iohao.game.action.skeleton.eventbus.EventBus\n * @see com.iohao.game.action.skeleton.eventbus.EventBusRunner\n */\npackage com.iohao.game.action.skeleton.eventbus;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/ext/spring/ActionFactoryBeanForSpring.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.ext.spring;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.core.ActionFactoryBean;\nimport com.iohao.game.action.skeleton.core.DependencyInjectionPart;\nimport lombok.Getter;\nimport org.springframework.beans.BeansException;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Objects;\n\n/**\n * spring集成\n * <pre>\n *     把 action 交由 spring 管理\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-03-22\n */\n@SuppressWarnings(\"unchecked\")\npublic class ActionFactoryBeanForSpring<T> implements ActionFactoryBean<T>, ApplicationContextAware {\n\n    private ApplicationContext applicationContext;\n    @Getter\n    boolean spring;\n\n    @Override\n    public T getBean(ActionCommand actionCommand) {\n        Class<?> actionControllerClazz = actionCommand.getActionControllerClazz();\n        return (T) this.applicationContext.getBean(actionControllerClazz);\n    }\n\n    @Override\n    public T getBean(Class<?> actionControllerClazz) {\n        return (T) this.applicationContext.getBean(actionControllerClazz);\n    }\n\n    @Override\n    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n        Objects.requireNonNull(applicationContext);\n\n        initDependencyInjectionPart();\n\n        this.spring = true;\n\n        this.applicationContext = applicationContext;\n    }\n\n    private void initDependencyInjectionPart() {\n        DependencyInjectionPart dependencyInjectionPart = DependencyInjectionPart.me();\n\n        dependencyInjectionPart.setInjection(true);\n\n        dependencyInjectionPart.setAnnotationClass(Component.class);\n\n        dependencyInjectionPart.setActionFactoryBean(this);\n    }\n\n    private ActionFactoryBeanForSpring() {\n    }\n\n    public static ActionFactoryBeanForSpring me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final ActionFactoryBeanForSpring ME = new ActionFactoryBeanForSpring();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/ext/spring/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 生态融合（集成扩展）- 在生态融合方面，ioGame 可以很方便的与 <a href=\"https://iohao.github.io/game/docs/manual/integration_spring\">spring 集成（4 行代码）</a>，从而能方便的使用其相关生态。\n * <pre>{@code\n * @SpringBootApplication\n * public class DemoSpringApplication {\n *     @Bean\n *     public ActionFactoryBeanForSpring actionFactoryBean() {\n *         // 将业务框架交给 spring 管理\n *         return ActionFactoryBeanForSpring.me();\n *     }\n * }\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2024-08-07\n */\npackage com.iohao.game.action.skeleton.ext.spring;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/i18n/Bundle.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.i18n;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Locale;\nimport java.util.Objects;\nimport java.util.ResourceBundle;\n\n/**\n * @author 渔民小镇\n * @date 2024-10-02\n * @since 21.18\n */\n@UtilityClass\npublic final class Bundle {\n    final String baseName = \"iohao\";\n    ResourceBundle bundle;\n\n    ResourceBundle getBundle() {\n        if (Objects.isNull(bundle)) {\n            bundle = ResourceBundle.getBundle(baseName, Locale.getDefault());\n        }\n\n        return bundle;\n    }\n\n    public String getMessage(String key) {\n        return getBundle().getString(key);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/i18n/MessageKey.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General  License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General  License for more details.\n *\n * You should have received a copy of the GNU Affero General  License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.i18n;\n\n/**\n * i18n message key, see iohao.properties\n *\n * @author 渔民小镇\n * @date 2024-10-02\n * @since 21.18\n */\npublic interface MessageKey {\n    String cmdName = \"cmdName\";\n    String gameExternalServer = \"gameExternalServer\";\n    String gameServerAmount = \"gameServerAmount\";\n    /** ExternalJoinEnum */\n    String connectionWay = \"connectionWay\";\n\n    /*  about brokerServer */\n    String gameBrokerServer = \"gameBrokerServer\";\n    String gameBrokerServerStartupMode = \"gameBrokerServerStartupMode\";\n    String gameBrokerServerStartupModeCluster = \"gameBrokerServerStartupModeCluster\";\n    String gameBrokerServerStartupModeStandalone = \"gameBrokerServerStartupModeStandalone\";\n    String gameBrokerServerConnectionAmount = \"gameBrokerServerConnectionAmount\";\n    String brokerClientRegistrationMessage = \"brokerClientRegistrationMessage\";\n\n    /* see PrintActionKit.java */\n    String businessFramework = \"businessFramework\";\n    String businessFrameworkPlugin = \"businessFrameworkPlugin\";\n    String printActionKitPrintClose = \"printActionKitPrintClose\";\n    String printActionKitPrintFull = \"printActionKitPrintFull\";\n    String printActionKitDataCodec = \"printActionKitDataCodec\";\n    String printActionKitCheckReturnType = \"printActionKitCheckReturnType\";\n\n    /* see DebugInOut.java */\n    String debugInOutThreadName = \"debugInOutThreadName\";\n    String debugInOutParamName = \"debugInOutParamName\";\n    String debugInOutReturnData = \"debugInOutReturnData\";\n    String debugInOutErrorCode = \"debugInOutErrorCode\";\n    String debugInOutErrorMsg = \"debugInOutErrorMsg\";\n    String debugInOutTime = \"debugInOutTime\";\n\n    /* see StatActionInOut.java */\n    String statActionInOutTimeRange = \"statActionInOutTimeRange\";\n    String statActionInOutStatAction = \"statActionInOutStatAction\";\n\n    /* see ThreadMonitorInOut.java */\n    String threadMonitorInOutThreadMonitor = \"threadMonitorInOutThreadMonitor\";\n    /* see TimeRangeInOut.java */\n    String timeRangeInOutDayTitle = \"timeRangeInOutDayTitle\";\n    String timeRangeInOutHourTitle = \"timeRangeInOutHourTitle\";\n    String timeRangeInOutMinuteTitle = \"timeRangeInOutMinuteTitle\";\n    /* see ActionCommandRegions.java */\n    String cmdMergeLimit = \"cmdMergeLimit\";\n    /* see ProtobufCheckActionParserListener.java */\n    String protobufAnnotationCheck = \"protobufAnnotationCheck\";\n\n    /* see TextDocumentGenerate.java */\n    String textDocumentTitle = \"textDocumentTitle\";\n    String textDocumentBroadcastTitle = \"textDocumentBroadcastTitle\";\n    String textDocumentCmd = \"textDocumentCmd\";\n    String textDocumentBroadcast = \"textDocumentBroadcast\";\n    String textDocumentErrorCodeTitle = \"textDocumentErrorCodeTitle\";\n\n    /* see DefaultUserHook.java */\n    String userHookInto = \"userHookInto\";\n    String userHookQuit = \"userHookQuit\";\n\n    /* see InternalAboutFlowContext.java */\n    String bindingUserId = \"bindingUserId\";\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/ExecutorSelectEnum.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.kit;\n\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.common.kit.concurrent.executor.*;\n\nimport java.io.Serializable;\n\n/**\n * 执行器选择枚举\n * <pre>\n *     具体阅读参考 DefaultRequestMessageClientProcessorHook 相关源码\n *\n *     可将枚举设置到 {@link HeadMetadata#setExecutorSelect(ExecutorSelectEnum)} 中。框架会在执行 action 前，根据 ExecutorSelectEnum 值来选择对应的执行器。\n *\n *     当为 null 或 userExecutor 时，使用 {@link ExecutorRegion#getUserThreadExecutorRegion()} 策略，该策略【保证线程安全】\n *     该策略可以确保同一玩家的 action 请求在同一线程执行器中执行。\n *\n *     当为 userVirtualExecutor 时，使用 {@link ExecutorRegion#getUserVirtualThreadExecutorRegion()} ()} 策略，该策略【不保证线程安全】\n *     该策略使用虚拟线程执行 action 请求，如果你能确定你的某些 action 执行较为耗时，且不需要保证线程的，可以使用该策略。\n *\n *     当为 currentThread 时，不使用任何线程执行器，而是在 netty 线程中执行 action 请求；该策略【不保证线程安全】\n *     (具体可阅读 RequestMessageClientProcessor、DefaultRequestMessageClientProcessorHook 相关源码)\n *\n *     customExecutor 则是预留给开发者的，如果框架提供的以上策略都满足不了业务的，可以考虑扩展 RequestMessageClientProcessorHook 接口\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-19\n * @see ExecutorRegion#getUserThreadExecutorRegion()\n * @see ExecutorRegion#getUserVirtualThreadExecutorRegion()\n */\npublic enum ExecutorSelectEnum implements Serializable {\n    /**\n     * 在用户线程执行器中执行\n     *\n     * @see ExecutorRegion#getUserThreadExecutorRegion()\n     */\n    userExecutor,\n    /**\n     * 在虚拟线程执行器中执行\n     *\n     * @see ExecutorRegion#getUserVirtualThreadExecutorRegion()\n     */\n    userVirtualExecutor,\n    /** netty 线程中执行 action 请求 */\n    currentThread,\n    /** 预留给开发者的 */\n    customExecutor\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/ExecutorSelectKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.kit;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * 线程执行器相关工具\n *\n * @author 渔民小镇\n * @date 2023-12-19\n */\n@UtilityClass\npublic class ExecutorSelectKit {\n    /**\n     * 执行业务框架 （执行 action）\n     *\n     * @param barSkeleton 业务框架\n     * @param flowContext flowContext\n     * @return true 表示请求被执行\n     */\n    public boolean processLogic(BarSkeleton barSkeleton, FlowContext flowContext) {\n        HeadMetadata headMetadata = flowContext.getHeadMetadata();\n        final ThreadExecutor threadExecutor = getThreadExecutor(barSkeleton, headMetadata);\n\n        if (Objects.isNull(threadExecutor)) {\n            return false;\n        }\n\n        flowContext.option(FlowAttr.threadExecutor, threadExecutor);\n\n        // 使用单例的 ThreadExecutorRegion 来处理，即使在同一进程中启动了多个逻辑服，也不会创建过多线程执行器，而是使用同一个。\n        threadExecutor.execute(() -> {\n            // 在当前线程执行器中执行业务框架\n            barSkeleton.handle(flowContext);\n        });\n\n        return true;\n    }\n\n    private ThreadExecutor getThreadExecutor(BarSkeleton barSkeleton, HeadMetadata headMetadata) {\n        final long executorIndex = getExecutorIndex(headMetadata);\n        final ExecutorRegion executorRegion = barSkeleton.getExecutorRegion();\n\n        final ExecutorSelectEnum executorSelect = headMetadata.getExecutorSelect();\n\n        return switch (executorSelect) {\n            case null -> executorRegion.getUserThreadExecutor(executorIndex);\n            case userVirtualExecutor -> executorRegion.getUserVirtualThreadExecutor(executorIndex);\n            case userExecutor -> executorRegion.getUserThreadExecutor(executorIndex);\n            default -> null;\n        };\n    }\n\n    public long getExecutorIndex(HeadMetadata headMetadata) {\n        var userId = headMetadata.getUserId();\n        if (userId != 0) {\n            return userId;\n        }\n\n        // 如果没有登录，使用 channelId 计算；如果 channelId 不存在，则使用 cmd\n        return Optional.ofNullable(headMetadata.getChannelId())\n                .map(String::hashCode)\n                .map(Math::abs)\n                .orElseGet(headMetadata::getCmdMerge);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/FixedCmd.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.kit;\n\nimport java.util.function.Consumer;\nimport java.util.stream.Stream;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-05\n */\npublic interface FixedCmd<V extends FixedCmd.CmdNode> {\n\n    V get(int cmdMerge);\n\n    void ifPresent(int cmdMerge, Consumer<V> consumer);\n\n    Stream<V> stream();\n\n    interface CmdNode {\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/LogicServerCreateKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.kit;\n\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilder;\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilderParamConfig;\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.internal.DebugInOut;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-13\n */\n@UtilityClass\npublic class LogicServerCreateKit {\n    List<ActionMethodInOut> inOutList = new LinkedList<>();\n\n    static {\n        inOutList.add(new DebugInOut());\n    }\n\n    public BarSkeletonBuilder createBuilder(BarSkeletonBuilderParamConfig config) {\n        // 业务框架构建器\n        var builder = config.createBuilder();\n\n        // 添加插件\n        inOutList.forEach(builder::addInOut);\n\n        return builder;\n    }\n\n    public BarSkeletonBuilder createBuilder(Class<?> actionControllerClass) {\n        // 业务框架构建器 配置\n        var config = new BarSkeletonBuilderParamConfig()\n                // 扫描 action 类所在包\n                .scanActionPackage(actionControllerClass);\n\n        return createBuilder(config);\n    }\n\n    public void removeInOut(Class<? extends ActionMethodInOut> inoutClass) {\n        Objects.requireNonNull(inoutClass);\n        inOutList.removeIf(actionMethodInOut -> inoutClass.equals(actionMethodInOut.getClass()));\n    }\n\n    public void addInOut(ActionMethodInOut inOut) {\n        Objects.requireNonNull(inOut);\n        inOutList.add(inOut);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/RangeBroadcast.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.kit;\n\nimport com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * 范围内的广播，这个范围指的是，指定某些用户进行广播。\n * <pre>\n *     在执行广播前，开发者可以自定义业务逻辑，如\n *     - 添加一些需要广播的用户\n *     - 删除一些不需要接收广播的用户\n *     - 可通过重写 logic、trick 方法来做一些额外扩展\n * </pre>\n * for example\n * <pre>{@code\n *         // example - 1\n *         new RangeBroadcast(flowContext)\n *                 // 需要广播的数据\n *                 .setResponseMessage(responseMessage)\n *                 // 添加需要接收广播的用户\n *                 .addUserId(1)\n *                 .addUserId(2)\n *                 .addUserId(List.of(3L, 4L, 5L))\n *                 // 排除一些用户，被排除的用户将不会接收到广播\n *                 .removeUserId(1)\n *                 .removeUserId(4)\n *                 // 执行广播，只有 2、3、5 可以接收到广播\n *                 .execute();\n *\n *         // example - 2\n *         new RangeBroadcast(flowContext)\n *                 // 需要广播的数据（路由、业务数据）\n *                 .setResponseMessage(cmdInfo, StringValue.of(\"hello\"))\n *                 // 添加需要接收广播的用户\n *                 .addUserId(1)\n *                 // 执行广播\n *                 .execute();\n *\n *         // example - 3\n *         BrokerClientContext brokerClient = ...;\n *         var aggregationContext = brokerClient.getCommunicationAggregationContext();\n *         new RangeBroadcast(aggregationContext)\n *                  // 需要广播的数据（路由、业务数据）\n *                 .setResponseMessage(cmdInfo, StringValue.of(\"hello\"))\n *                 // 添加需要接收广播的用户\n *                 .addUserId(1)\n *                 // 执行广播\n *                 .execute();\n * }\n * </pre>\n * 此外，还支持协议碎片及 List。关于协议碎片可阅读 <a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片 - 文档</a>\n * for example\n * <pre>{@code\n *     // ------------ object ------------\n *     // 广播单个对象\n *     DemoBroadcastMessage message = new DemoBroadcastMessage();\n *     message.msg = \"helloBroadcast --- 1\";\n *\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessage(cmdInfo, message);\n *\n *     List<DemoBroadcastMessage> messageList = List.of(message);\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessageList(cmdInfo, messageList);\n *\n *     // ------------ int ------------\n *\n *     // 广播 int\n *     int intValue = 1;\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessage(cmdInfo, intValue);\n *\n *     // 广播 int list\n *     List<Integer> intValueList = List.of(1, 2);\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessageIntList(cmdInfo, intValueList);\n *\n *     // ------------ long ------------\n *\n *     // 广播 long\n *     long longValue = 1L;\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessage(cmdInfo, longValue);\n *\n *     // 广播 long list\n *     List<Long> longValueList = List.of(1L, 2L);\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessageLongList(cmdInfo, longValueList);\n *\n *     // ------------ String ------------\n *\n *     // 广播 String\n *     String stringValue = \"1\";\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessage(cmdInfo, stringValue);\n *\n *     // 广播 String list\n *     List<String> stringValueList = List.of(\"1L\", \"2L\");\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessageStringList(cmdInfo, stringValueList);\n *\n *     // ------------ boolean ------------\n *\n *     // 广播 boolean\n *     boolean boolValue = true;\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessage(cmdInfo, boolValue);\n *\n *     // 广播 boolean list\n *     List<Boolean> boolValueList = List.of(true, false);\n *     new RangeBroadcast(flowContext)\n *             .setResponseMessageBoolList(cmdInfo, boolValueList);\n * }\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-04-23\n * @since 21.8\n */\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class RangeBroadcast implements RangeBroadcaster {\n    @Getter(AccessLevel.PROTECTED)\n    final CommunicationAggregationContext aggregationContext;\n    /** 需要推送的 userId 列表 */\n    @Getter(AccessLevel.PROTECTED)\n    final Set<Long> userIds = new NonBlockingHashSet<>();\n    /** 响应（广播）数据 ResponseMessage */\n    @Getter(AccessLevel.PROTECTED)\n    ResponseMessage responseMessage;\n    /** 是否执行发送领域事件操作: true 执行推送操作 */\n    boolean doSend = true;\n    /** 检查 userIds ；当值为 true 时，userIds 必须有元素 */\n    boolean checkEmptyUser = false;\n\n    /**\n     * create by CommunicationAggregationContext\n     *\n     * @param aggregationContext 网络通讯聚合接口\n     */\n    public RangeBroadcast(CommunicationAggregationContext aggregationContext) {\n        Objects.requireNonNull(aggregationContext);\n        this.aggregationContext = aggregationContext;\n    }\n\n    /**\n     * create by CommunicationAggregationContext\n     *\n     * @param flowContext flowContext CommunicationAggregationContext\n     */\n    public RangeBroadcast(FlowContext flowContext) {\n        this(flowContext.option(FlowAttr.aggregationContext));\n    }\n\n    /**\n     * 检测空用户，如果没有任何用户，广播（推送）时将触发异常\n     *\n     * @return this\n     */\n    public RangeBroadcaster enableEmptyUserCheck() {\n        this.checkEmptyUser = false;\n        return this;\n    }\n\n    @Override\n    public Set<Long> listUserId() {\n        return this.userIds;\n    }\n\n    /**\n     * 设置响应的广播数据 ResponseMessage\n     *\n     * @param responseMessage ResponseMessage\n     * @return this\n     */\n    public RangeBroadcaster setResponseMessage(ResponseMessage responseMessage) {\n        this.responseMessage = responseMessage;\n        return this;\n    }\n\n    /**\n     * 响应消息到远程端（用户、玩家）\n     * <pre>\n     *     模板方法模式：\n     *         在一个方法中定义一个算法的骨架，而将一些步骤延迟到子类中。\n     *         模板方法使得子类可以在不改变算法结构的情况下，重新定义算法中的某些步骤。\n     *\n     *         要点：\n     *         - “模板方法”定义了算法的步骤，把这些步骤的实现延迟到子类。\n     *         - 模板方法模式为我们提供了一种代码复用的重要技巧。\n     *         - 模板方法的抽象类可以定义具体方法、抽象方法和钩子。\n     *         - 抽象方法由子类实现。\n     *         - 钩子是一种方法，它在抽象类中不做事，或者只做默认的事情，子类可以选择要不要去覆盖它。\n     *         - 为了防止子类改变模板方法中的算法，可以将模板方法声明为final。\n     *         - 好莱坞原则告诉我们，将决策权放在高层模块中，以便决定如何以及何时调用低层模块。\n     *         - 你将在真实世界代码中看到模板方法模式的许多变体，不要期待它们全都是一眼就可以被你一眼认出的。\n     *         - 策略模式和模板方法模式都封装算法，一个用组合，一个用继承。\n     *         - 工厂方法是模板方法的一种特殊版本。\n     * </pre>\n     */\n    public final void execute() {\n        // 子类可根据需要来构建响应内容\n        this.logic();\n\n        // 钩子方法，see disableSend()\n        if (!this.doSend) {\n            return;\n        }\n\n        // 在将数据推送前调用的钩子方法\n        this.trick();\n\n        Objects.requireNonNull(this.responseMessage);\n\n        // 开始广播\n        this.broadcast();\n    }\n\n    /**\n     * 在将数据推送到调用方之前，触发的方法\n     * <pre>\n     *     可以做一些逻辑，在逻辑中可以决定是否执行推送\n     *     {@code\n     *         // 不执行推送数据的操作\n     *         this.disableSend()\n     *     }\n     * </pre>\n     */\n    protected void logic() {\n    }\n\n    /**\n     * 小把戏 (钩子方法)，子类可以做些其他的事情；执行广播（推送）之前，触发的方法。\n     */\n    protected void trick() {\n    }\n\n    /**\n     * 广播数据\n     */\n    protected void broadcast() {\n        boolean emptyUser = this.userIds.isEmpty();\n        if (checkEmptyUser && emptyUser) {\n            // 请添加消息推送人\n            ThrowKit.ofRuntimeException(\"Please add a message sender\");\n        }\n\n        // 推送响应（广播消息）给指定的用户列表\n        if (!emptyUser) {\n            this.aggregationContext.broadcast(this.responseMessage, this.userIds);\n        }\n    }\n\n    /**\n     * 不执行推送数据的操作\n     */\n    protected void disableSend() {\n        this.doSend = false;\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/RangeBroadcaster.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.kit;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.protocol.wrapper.*;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 范围内的广播接口，这个范围指的是，指定某些用户进行广播。\n * <pre>\n *     在执行广播前，开发者可以自定义业务逻辑，如\n *     - 添加一些需要广播的用户\n *     - 删除一些不需要接收广播的用户\n * </pre>\n * for example\n * <pre>{@code\n *         // example - 1\n *         RangeBroadcaster.of(flowContext)\n *                 // 需要广播的数据\n *                 .setResponseMessage(responseMessage)\n *                 // 添加需要接收广播的用户\n *                 .addUserId(1)\n *                 .addUserId(2)\n *                 .addUserId(List.of(3L, 4L, 5L))\n *                 // 排除一些用户，被排除的用户将不会接收到广播\n *                 .removeUserId(1)\n *                 .removeUserId(4)\n *                 // 执行广播，只有 2、3、5 可以接收到广播\n *                 .execute();\n *\n *         // example - 2\n *         RangeBroadcaster.of(flowContext)\n *                 // 需要广播的数据（路由、业务数据）\n *                 .setResponseMessage(cmdInfo, StringValue.of(\"hello\"))\n *                 // 添加需要接收广播的用户\n *                 .addUserId(1)\n *                 // 执行广播\n *                 .execute();\n *\n *         // example - 3\n *         BrokerClientContext brokerClient = ...;\n *         var aggregationContext = brokerClient.getCommunicationAggregationContext();\n *         RangeBroadcaster.of(aggregationContext)\n *                  // 需要广播的数据（路由、业务数据）\n *                 .setResponseMessage(cmdInfo, StringValue.of(\"hello\"))\n *                 // 添加需要接收广播的用户\n *                 .addUserId(1)\n *                 // 执行广播\n *                 .execute();\n * }\n * </pre>\n * 此外，还支持协议碎片及 List。关于协议碎片可阅读 <a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片 - 文档</a>\n * for example\n * <pre>{@code\n *     // ------------ object ------------\n *     // 广播 object\n *     DemoBroadcastMessage message = new DemoBroadcastMessage();\n *     message.msg = \"helloBroadcast --- 1\";\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessage(cmdInfo, message);\n *\n *     // 广播 object list\n *     List<DemoBroadcastMessage> messageList = List.of(message);\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessageList(cmdInfo, messageList);\n *\n *     // ------------ int ------------\n *     // 广播 int\n *     int intValue = 1;\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessage(cmdInfo, intValue);\n *\n *     // 广播 int list\n *     List<Integer> intValueList = List.of(1, 2);\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessageIntList(cmdInfo, intValueList);\n *\n *     // ------------ long ------------\n *     // 广播 long\n *     long longValue = 1L;\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessage(cmdInfo, longValue);\n *\n *     // 广播 long list\n *     List<Long> longValueList = List.of(1L, 2L);\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessageLongList(cmdInfo, longValueList);\n *\n *     // ------------ String ------------\n *     // 广播 String\n *     String stringValue = \"1\";\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessage(cmdInfo, stringValue);\n *\n *     // 广播 String list\n *     List<String> stringValueList = List.of(\"1L\", \"2L\");\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessageStringList(cmdInfo, stringValueList);\n *\n *     // ------------ boolean ------------\n *     // 广播 boolean\n *     boolean boolValue = true;\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessage(cmdInfo, boolValue);\n *\n *     // 广播 boolean list\n *     List<Boolean> boolValueList = List.of(true, false);\n *     RangeBroadcaster.of(flowContext)\n *             .setResponseMessageBoolList(cmdInfo, boolValueList);\n * }\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-06-02\n * @see RangeBroadcast impl\n * @since 21.9\n */\npublic interface RangeBroadcaster {\n    /**\n     * 接收广播的用户\n     *\n     * @return 接收广播的用户\n     */\n    Set<Long> listUserId();\n\n    /**\n     * 设置响应的广播数据 ResponseMessage\n     *\n     * @param responseMessage ResponseMessage\n     * @return this\n     */\n    RangeBroadcaster setResponseMessage(ResponseMessage responseMessage);\n\n    /**\n     * 响应消息到远程端（用户、玩家）\n     */\n    void execute();\n\n    /**\n     * 创建一个默认的 RangeBroadcaster 对象（框架内置）\n     *\n     * @param aggregationContext 框架网络通讯聚合接口\n     * @return RangeBroadcaster\n     */\n    static RangeBroadcaster of(CommunicationAggregationContext aggregationContext) {\n        return new RangeBroadcast(aggregationContext);\n    }\n\n    /**\n     * 创建一个默认的 RangeBroadcaster 对象（框架内置）\n     *\n     * @param flowContext FlowContext\n     * @return RangeBroadcaster\n     */\n    static RangeBroadcaster of(FlowContext flowContext) {\n        return new RangeBroadcast(flowContext);\n    }\n\n    /**\n     * 接收广播的用户\n     *\n     * @param userIds userIds\n     * @return this\n     */\n    default RangeBroadcaster addUserId(Collection<Long> userIds) {\n        this.listUserId().addAll(userIds);\n        return this;\n    }\n\n    /**\n     * 接收广播的用户\n     *\n     * @param userId userId\n     * @return this\n     */\n    default RangeBroadcaster addUserId(long userId) {\n        this.listUserId().add(userId);\n        return this;\n    }\n\n    /**\n     * 添加接收广播的用户，顺带排除一个不需要接收广播的用户\n     *\n     * @param userIds       接收广播的 userIds\n     * @param excludeUserId 需要排除的 userId\n     * @return this\n     */\n    default RangeBroadcaster addUserId(Collection<Long> userIds, long excludeUserId) {\n        return this.addUserId(userIds).removeUserId(excludeUserId);\n    }\n\n    /**\n     * 排除 userId\n     *\n     * @param excludeUserId 需要排除的 userId\n     * @return this\n     */\n    default RangeBroadcaster removeUserId(long excludeUserId) {\n        if (excludeUserId > 0) {\n            this.listUserId().remove(excludeUserId);\n        }\n\n        return this;\n    }\n\n    /**\n     * 设置响应的广播数据\n     *\n     * @param cmdInfo 路由\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessage(CmdInfo cmdInfo) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo);\n        return this.setResponseMessage(responseMessage);\n    }\n\n    /**\n     * 设置响应的广播数据\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessage(CmdInfo cmdInfo, Object bizData) {\n        var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData);\n        return this.setResponseMessage(responseMessage);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link ByteValueList} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessageList(CmdInfo cmdInfo, Collection<?> bizData) {\n        var value = ByteValueList.ofList(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link IntValue} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessage(CmdInfo cmdInfo, int bizData) {\n        var value = IntValue.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link IntValueList} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessageIntList(CmdInfo cmdInfo, List<Integer> bizData) {\n        var value = IntValueList.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link LongValue} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessage(CmdInfo cmdInfo, long bizData) {\n        var value = LongValue.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link LongValueList} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessageLongList(CmdInfo cmdInfo, List<Long> bizData) {\n        var value = LongValueList.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link StringValue} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessage(CmdInfo cmdInfo, String bizData) {\n        var value = StringValue.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link StringValueList} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessageStringList(CmdInfo cmdInfo, List<String> bizData) {\n        var value = StringValueList.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link BoolValue} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessage(CmdInfo cmdInfo, boolean bizData) {\n        var value = BoolValue.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n\n    /**\n     * 设置响应的广播数据。业务数据会使用 {@link BoolValueList} 来包装（<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">.协议碎片</a>）。\n     *\n     * @param cmdInfo 路由\n     * @param bizData 业务数据\n     * @return this\n     */\n    default RangeBroadcaster setResponseMessageBoolList(CmdInfo cmdInfo, List<Boolean> bizData) {\n        var value = BoolValueList.of(bizData);\n        return this.setResponseMessage(cmdInfo, value);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 工具包，如：线程执行器相关工具、范围内的广播 ..等\n *\n * @author 渔民小镇\n * @date 2024-08-07\n */\npackage com.iohao.game.action.skeleton.kit;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - <a href=\"https://iohao.github.io/game/docs/core/framework\">业务框架简介</a>。\n * <pre>\n *     如果说  sofa-bolt 是为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上，而业务框架正是解决业务逻辑如何方便实现这一问题上。\n *     业务框架是游戏框架的一部分，职责是简化程序员的业务逻辑实现，业务框架使程序员能够快速的开始编写游戏业务。\n * </pre>\n * <p>\n * for example\n * <pre>{@code\n * @ActionController(1)\n * public class DemoAction {\n *     @ActionMethod(0)\n *     public HelloReq here(HelloReq helloReq) {\n *         // 业务数据\n *         var newHelloReq = new HelloReq();\n *         newHelloReq.name = helloReq.name + \", I'm here \";\n *         return newHelloReq;\n *     }\n *\n *     // 注意，这个方法只是为了演示而写的；（ioGame21 开始支持）\n *     // 效果与上面的方法一样，只不过是用广播（推送）的方式将数据返回给请求方\n *     @ActionMethod(0)\n *     public void here(HelloReq helloReq, FlowContext flowContext) {\n *         // 业务数据\n *         var newHelloReq = new HelloReq();\n *         newHelloReq.name = helloReq.name + \", I'm here \";\n *\n *         flowContext.broadcastMe(newHelloReq);\n *     }\n *\n *     // 跨服调用示例，下面分别展示了同步与异步回调的写法\n *     void testShowInvokeModule(FlowContext flowContext) {\n *         var cmdInfo = CmdInfo.of(1,0);\n *         var yourData = ... 你的请求参数\n *\n *         // 跨服请求（异步回调 - 无阻塞）-- 路由、请求参数、回调。\n *         flowContext.invokeModuleMessageAsync(cmdInfo, yourData, responseMessage -> {\n *             var helloReq = responseMessage.getData(HelloReq.class);\n *             // --- 此异步回调，具备全链路调用日志跟踪 ---\n *             log.info(\"异步回调 : {}\", helloReq);\n *         });\n *\n *\n *         // 跨服请求（同步 - 阻塞）-- 路由、请求参数。\n *         ResponseMessage responseMessage = flowContext.invokeModuleMessage(cmdInfo, yourData);\n *         var helloReq = responseMessage.getData(HelloReq.class);\n *         log.info(\"同步调用 : {}\", helloReq);\n *     }\n * }\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2022-09-23\n */\npackage com.iohao.game.action.skeleton;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/BarMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.core.codec.DataSelfEncode;\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport com.iohao.game.common.consts.CommonConst;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n * 消息基类\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PROTECTED)\npublic abstract sealed class BarMessage implements Serializable permits RequestMessage, ResponseMessage {\n    @Serial\n    private static final long serialVersionUID = 562068269463876111L;\n\n    /** 响应码: 0:成功, 其他表示有错误 */\n    int responseStatus;\n    /** 异常信息、JSR380 验证信息 */\n    String validatorMsg;\n\n    /** 元信息 */\n    HeadMetadata headMetadata;\n\n    /** 业务数据的 class 信息 */\n    String dataClass;\n    /** 实际请求的业务参数 byte[] */\n    byte[] data;\n\n    public BarMessage setData(byte[] data) {\n        this.data = data;\n        return this;\n    }\n\n    public BarMessage setData(Object data) {\n        if (Objects.isNull(data)) {\n            return this.setData(CommonConst.emptyBytes);\n        }\n\n        // 保存一下业务数据的 class\n        this.dataClass = data.getClass().getName();\n\n        if (data instanceof DataSelfEncode dataSelfEncode) {\n            return this.setData(dataSelfEncode.encodeData());\n        }\n\n        byte[] bytes = DataCodecKit.encode(data);\n        return this.setData(bytes);\n    }\n\n    /**\n     * 设置验证的错误信息\n     *\n     * @param validatorMsg 错误信息\n     * @return this\n     */\n    public BarMessage setValidatorMsg(String validatorMsg) {\n        if (validatorMsg != null) {\n            this.validatorMsg = validatorMsg;\n        }\n\n        return this;\n    }\n\n    public BarMessage setError(MsgExceptionInfo msgExceptionInfo) {\n        this.responseStatus = msgExceptionInfo.getCode();\n        this.validatorMsg = msgExceptionInfo.getMsg();\n        return this;\n    }\n\n    /**\n     * 是否有错误\n     * <pre>\n     *     this.errorCode != 0 表示有错误\n     * </pre>\n     *\n     * @return true 有错误码\n     */\n    public boolean hasError() {\n        return this.responseStatus != 0;\n    }\n\n    public boolean success() {\n        return !this.hasError();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/HeadMetadata.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.Ignore;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.flow.internal.DefaultActionAfter;\nimport com.iohao.game.action.skeleton.kit.ExecutorSelectEnum;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * Meta information\n * <p>\n * 元信息\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PROTECTED)\npublic final class HeadMetadata implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -472575113683576693L;\n\n    /** userId */\n    long userId;\n\n    /**\n     * 合并两个参数,分别存放在 [高16 和 低16]\n     * <pre>\n     *     cmd 目标路由\n     *     subCmd 目标子路由\n     *\n     *     例如 cmd = 600; subCmd = 700;\n     *     merge 的结果: 39322300\n     *     那么merge对应的二进制是: 0000 0010 0101 1000 0000 0010 1011 1100\n     *\n     *     see {@link CmdInfo}\n     * </pre>\n     */\n    int cmdMerge;\n\n    /**\n     * Source logic server client ID\n     * <p>\n     * If the request is initiated by an external server,\n     * this value should be the external server's clientId (a server's unique identifier).\n     * <p>\n     * 来源逻辑服 client id\n     * <pre>\n     *     比如是对外服发起的请求，这个来源就是对外服的 clientId\n     *     clientId 指的是 服务器的唯一id\n     *\n     *     see {@link com.iohao.game.common.kit.HashKit}\n     * </pre>\n     */\n    int sourceClientId;\n\n    /**\n     * Target logic server endPointClientId\n     * Used to specify which server should process the request\n     * <p>\n     * For example, with two chess servers (game logic servers) A and B:\n     * When players Player1 and Player2 are in a match, if they started playing on Chess Server A,\n     * then all subsequent requests must be assigned to Chess Server A for processing.\n     * <p>\n     * endPointClientId refers to the server's unique identifier.\n     * <p>\n     * see {@link com.iohao.game.common.kit.HashKit}\n     * <p>\n     * 目标逻辑服 endPointClientId\n     * <pre>\n     *     用于指定请求由哪个服务器处理\n     *\n     *     比如两个象棋服（游戏逻辑服） A，B\n     *     玩家李雷和韩梅梅在对局时，如果在 象棋服A 玩的，那么之后的请求都要分配给 象棋服A 来处理。\n     *\n     *     endPointClientId 指的是 服务器的唯一id\n     *\n     *     see {@link com.iohao.game.common.kit.HashKit}\n     * </pre>\n     */\n    int endPointClientId;\n\n    /**\n     * rpc type\n     * <pre>\n     *     see : com.alipay.remoting.rpc.RpcCommandType\n     *\n     *     在 bolt 中， 调用方使用 com.alipay.remoting.rpc.RpcServer或 com.alipay.remoting.rpc.RpcClient 的 oneway 方法，\n     *     则 AsyncContext.sendResponse 无法回传响应；原因可阅读 com.alipay.remoting.rpc.protocol.RpcRequestProcessor#sendResponseIfNecessary 源码。\n     *\n     *     业务框架保持与 bolt 的风格一至，使用 RpcCommandType。不同的是，业务框架会用 RpcCommandType 区别使用什么方式来发送响应。\n     *\n     *     如果 rpcCommandType != RpcCommandType.REQUEST_ONEWAY，就使用 com.alipay.remoting.AsyncContext#sendResponse 来发送响应。\n     *     具体发送逻辑可读 {@link DefaultActionAfter} 源码\n     * </pre>\n     */\n    byte rpcCommandType;\n\n    /**\n     * Extended field. Developers can use this field to extend meta-information for special business needs.\n     * The data in this field will be included with every request.\n     * <p>\n     * 扩展字段，开发者有特殊业务可以通过这个字段来扩展元信息，该字段的信息会跟随每一个请求。\n     */\n    byte[] attachmentData;\n\n    /**\n     * netty channelId\n     * <p>\n     * The framework stores Netty's channelId to allow the external server to look up the corresponding connection.\n     * <p>\n     * After a player logs in, the framework will use the player's userId to locate the corresponding connection (channel) instead of the channelId,\n     * because the channelId string is too long, and transmitting this value to the logic server each time would slightly impact performance.\n     * <p>\n     * Once the player logs in, the framework will no longer use this property.\n     * However, if needed, developers can repurpose this field for their own use.\n     * <pre>\n     *     框架存放 netty 的 channelId 是为了能在对外服查找到对应的连接。\n     *     当玩家登录后，框架将会使用玩家的 userId 来查找对应的连接（channel），而不是 channelId ；\n     *     因为 channelId 的字符串太长了，每次将该值传输到逻辑服会小小的影响性能。\n     *\n     *     当玩家登录后，框架将不会使用该属性了；开发者如果有需要，可以把这个字段利用起来。\n     * </pre>\n     */\n    String channelId;\n\n    /** 消息标记号；由前端请求时设置，服务器响应时会携带上 */\n    int msgId;\n\n    /**\n     * Framework's internal fields. Future changes are likely to be significant, so developers should refrain from using them.\n     * <p>\n     * 框架自用字段。将来变化可能较大，开发者请不要使用。\n     */\n    int stick;\n\n    /**\n     * The IDs of multiple game logic servers bound to the player\n     * <pre>\n     *     All requests related to this game logic server will be routed to the bound game logic server for processing.\n     *     Even if multiple game logic servers of the same type are running, requests will still be directed to the originally bound server.\n     * </pre>\n     * <p>\n     * 玩家绑定的多个游戏逻辑服 id\n     * <pre>\n     *     所有与该游戏逻辑服相关的请求都将被分配给已绑定的游戏逻辑服处理。\n     *     即使启动了多个同类型的游戏逻辑服，该请求仍将被分配给已绑定的游戏逻辑服处理。\n     * </pre>\n     */\n    int[] bindingLogicServerIds;\n    /**\n     * Framework's internal fields. Future changes are likely to be significant, so developers should refrain from using them.\n     * <p>\n     * 框架自用字段。将来变化可能较大，开发者请不要使用。\n     */\n    int cacheCondition;\n    /**\n     * Custom data, a field reserved specifically for developers.\n     * Developers can use this field to pass custom data.\n     * The field is entirely user-defined—the framework will neither process nor validate its contents.\n     * Developers can leverage it to transmit any data, including custom objects.\n     * <p>\n     * 自定义数据，专为开发者预留的一个字段，开发者可以利用该字段来传递自定义数据。\n     * 该字段由开发者自己定义，框架不会对数据做任何处理，也不会做任何检查，开发者可以利用该字段来传递任何数据，包括自定义对象。\n     */\n    byte[] customData;\n    ExecutorSelectEnum executorSelect;\n    String traceId;\n    /**\n     * Framework's internal fields. Future changes are likely to be significant, so developers should refrain from using them.\n     * <p>\n     * 框架自用字段。将来变化可能较大，开发者请不要使用。\n     */\n    byte[] userProcessorExecutorSelectorBytes;\n    /**\n     * Temporary variable\n     * <p>\n     * 临时变量\n     */\n    transient Object other;\n    transient int withNo;\n    /**\n     * Request command type: 0 Heartbeat, 1 Business\n     * <p>\n     * 请求命令类型: 0 心跳，1 业务\n     */\n    transient int cmdCode;\n    /**\n     * Protocol switch: Used for protocol-level control (e.g., security encryption verification). 0: No verification\n     * <p>\n     * 协议开关，用于一些协议级别的开关控制，比如 安全加密校验等。 : 0 不校验\n     */\n    transient int protocolSwitch;\n    /** 预留 inet */\n    @Ignore\n    transient Object inetSocketAddress;\n    /** 原始的游戏对外服协议引用（开发者可自定义，这里会保存一个引用，方便特殊业务获取） */\n    @Ignore\n    transient Object externalMessage;\n\n    public HeadMetadata setCmdInfo(CmdInfo cmdInfo) {\n        this.cmdMerge = cmdInfo.getCmdMerge();\n        return this;\n    }\n\n    public CmdInfo getCmdInfo() {\n        return CmdInfo.of(this.cmdMerge);\n    }\n\n    public HeadMetadata setCmdMerge(int cmdMerge) {\n        this.cmdMerge = cmdMerge;\n        return this;\n    }\n\n    /**\n     * Simple clone\n     * <p>\n     * Usage scenario （使用场景）\n     * <pre>\n     *     Can be used when communicating with other game logic servers.\n     *     The method assigns necessary player attributes to HeadMetadata.\n     *\n     *     （与其他游戏逻辑服通信时可以使用，方法中给 HeadMetadata 赋值了玩家的必要属性）\n     *     userId、attachmentData、channelId、bindingLogicServerIds、customData\n     *     traceId、executorSelect\n     * </pre>\n     * Unassigned by default. Set manually when required. （以下属性不会赋值，如有需要，请自行赋值）\n     * <pre>\n     *     cmdMerge\n     *     sourceClientId\n     *     endPointClientId\n     *     rpcCommandType\n     *     msgId\n     * </pre>\n     *\n     * @return HeadMetadata\n     */\n    public HeadMetadata cloneHeadMetadata() {\n\n        HeadMetadata headMetadata = new HeadMetadata();\n        headMetadata.userId = this.userId;\n        headMetadata.attachmentData = this.attachmentData;\n        headMetadata.channelId = this.channelId;\n        headMetadata.bindingLogicServerIds = this.bindingLogicServerIds;\n        headMetadata.customData = this.customData;\n        headMetadata.traceId = this.traceId;\n        headMetadata.executorSelect = this.executorSelect;\n\n        return headMetadata;\n    }\n\n    public HeadMetadata cloneAll() {\n        HeadMetadata headMetadata = cloneHeadMetadata();\n        headMetadata.cmdMerge = this.cmdMerge;\n        headMetadata.sourceClientId = this.sourceClientId;\n        headMetadata.endPointClientId = this.endPointClientId;\n        headMetadata.rpcCommandType = this.rpcCommandType;\n        headMetadata.msgId = this.msgId;\n\n        headMetadata.stick = this.stick;\n        headMetadata.cacheCondition = this.cacheCondition;\n\n        headMetadata.other = this.other;\n        headMetadata.withNo = this.withNo;\n        headMetadata.cmdCode = this.cmdCode;\n        headMetadata.protocolSwitch = this.protocolSwitch;\n        headMetadata.inetSocketAddress = this.inetSocketAddress;\n        headMetadata.externalMessage = this.externalMessage;\n\n        return headMetadata;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getExternalMessage() {\n        return (T) this.externalMessage;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/RequestMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\n\nimport java.io.Serial;\nimport java.util.Objects;\n\n/**\n * 请求参数\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic final class RequestMessage extends BarMessage {\n    @Serial\n    private static final long serialVersionUID = 8564408386704453534L;\n\n    public ResponseMessage createResponseMessage() {\n        ResponseMessage responseMessage = new ResponseMessage();\n\n        this.settingCommonAttr(responseMessage);\n\n        return responseMessage;\n    }\n\n    public void settingCommonAttr(final ResponseMessage responseMessage) {\n        // response 与 request 使用的 headMetadata 为同一引用\n        responseMessage.setHeadMetadata(this.headMetadata);\n    }\n\n    /**\n     * 设置自身属性到 request 中\n     *\n     * @param requestMessage request\n     */\n    public void copyTo(RequestMessage requestMessage) {\n        requestMessage.responseStatus = this.responseStatus;\n        requestMessage.validatorMsg = this.validatorMsg;\n        requestMessage.headMetadata = this.headMetadata;\n        requestMessage.dataClass = this.dataClass;\n        requestMessage.data = this.data;\n    }\n\n    /**\n     * 创建 RequestMessage 时，附带当前 RequestMessage 对象的一些信息\n     * <pre>\n     *     使用场景：与其他游戏逻辑服通信时可以使用\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @return 新的 RequestMessage\n     */\n    public RequestMessage createRequestMessage(CmdInfo cmdInfo) {\n        return createRequestMessage(cmdInfo, null);\n    }\n\n    /**\n     * 创建 RequestMessage 时，附带当前 RequestMessage 对象的一些信息\n     * <pre>\n     *     使用场景：与其他游戏逻辑服通信时可以使用\n     * </pre>\n     *\n     * @param cmdInfo 路由\n     * @param data    请求参数\n     * @return 新的 RequestMessage\n     */\n    public RequestMessage createRequestMessage(CmdInfo cmdInfo, Object data) {\n        HeadMetadata metadata = this.headMetadata\n                .cloneHeadMetadata()\n                .setCmdInfo(cmdInfo);\n\n        var requestMessage = new RequestMessage();\n        requestMessage.setHeadMetadata(metadata);\n\n        if (Objects.nonNull(data)) {\n            requestMessage.setData(data);\n        }\n\n        return requestMessage;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/ResponseMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.wrapper.*;\n\nimport java.io.Serial;\nimport java.util.List;\n\n/**\n * 响应参数\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic final class ResponseMessage extends BarMessage {\n    @Serial\n    private static final long serialVersionUID = 2501490581523234975L;\n\n    /**\n     * get 业务数据\n     *\n     * @param clazz biz data clazz\n     * @param <T>   t\n     * @return 业务数据\n     */\n    public <T> T getData(Class<T> clazz) {\n        return this.getValue(clazz);\n    }\n\n    /**\n     * get 业务数据，与 {@link ResponseMessage#getData(Class)} 功能一样，该方法只是为了与模拟客户端命名保持一致。\n     *\n     * @param clazz biz data clazz\n     * @param <T>   t\n     * @return 业务数据\n     */\n    public <T> T getValue(Class<? extends T> clazz) {\n        return DataCodecKit.decode(this.data, clazz);\n    }\n\n    /**\n     * get list 业务数据\n     *\n     * @param clazz biz data clazz\n     * @param <T>   t\n     * @return list 业务数据\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> List<T> listValue(Class<? extends T> clazz) {\n        return (List<T>) this.getValue(ByteValueList.class)\n                .values\n                .stream()\n                .map(v -> DataCodecKit.decode(v, clazz))\n                .toList();\n    }\n\n    /**\n     * get String 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public String getString() {\n        return this.getValue(StringValue.class).value;\n    }\n\n    /**\n     * get list String 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<String> listString() {\n        return this.getValue(StringValueList.class).values;\n    }\n\n    /**\n     * get int 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public int getInt() {\n        return this.getValue(IntValue.class).value;\n    }\n\n    /**\n     * get list int 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<Integer> listInt() {\n        return this.getValue(IntValueList.class).values;\n    }\n\n    /**\n     * get long 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public long getLong() {\n        return this.getValue(LongValue.class).value;\n    }\n\n    /**\n     * get list long 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<Long> listLong() {\n        return this.getValue(LongValueList.class).values;\n    }\n\n    /**\n     * get boolean 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public boolean getBoolean() {\n        return this.getValue(BoolValue.class).value;\n    }\n\n    /**\n     * get list boolean 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<Boolean> listBoolean() {\n        return this.getValue(BoolValueList.class).values;\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/collect/RequestCollectMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.collect;\n\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 模块之间的访问，访问【同类型】的多个逻辑服\n * <pre>\n *     如： 模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据\n *     是把多个相同逻辑服结果聚合在一起\n *\n *     具体的意思可以参考文档中的说明：\n *     <a href=\"https://iohao.github.io/game/docs/communication/request_multiple_response\">request_multiple_response</a>\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-22\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class RequestCollectMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 4271692369352579162L;\n    RequestMessage requestMessage;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/collect/ResponseCollectItemMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.collect;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.protocol.wrapper.*;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * 逻辑服结果与逻辑服的信息\n *\n * @author 渔民小镇\n * @date 2022-05-22\n */\n@Getter\n@Setter\n@ToString\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ResponseCollectItemMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -7655620321337836719L;\n    String logicServerId;\n    ResponseMessage responseMessage;\n\n    public ResponseCollectItemMessage() {\n    }\n\n    public ResponseCollectItemMessage(String logicServerId, ResponseMessage responseMessage) {\n        this.logicServerId = logicServerId;\n        this.responseMessage = responseMessage;\n    }\n\n    public boolean hasData() {\n        byte[] data = responseMessage.getData();\n        return data != null && data.length != 0;\n    }\n\n    /**\n     * get 业务数据\n     *\n     * @param dataClazz biz data clazz\n     * @param <T>       t\n     * @return 业务数据\n     */\n    public <T> T getData(Class<T> dataClazz) {\n\n        if (!this.hasData()) {\n            return null;\n        }\n\n        return this.getValue(dataClazz);\n    }\n\n    /**\n     * get 业务数据，与 {@link ResponseMessage#getData(Class)} 功能一样，该方法只是为了与模拟客户端命名保持一致。\n     *\n     * @param clazz biz data clazz\n     * @param <T>   t\n     * @return 业务数据，一定不为 null\n     */\n    public <T> T getValue(Class<? extends T> clazz) {\n        return responseMessage.getData(clazz);\n    }\n\n    /**\n     * get list 业务数据\n     *\n     * @param clazz biz data clazz\n     * @param <T>   t\n     * @return list 业务数据\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> List<T> listValue(Class<? extends T> clazz) {\n        return (List<T>) this.getValue(ByteValueList.class)\n                .values\n                .stream()\n                .map(v -> DataCodecKit.decode(v, clazz))\n                .toList();\n    }\n\n    /**\n     * get String 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public String getString() {\n        return this.getValue(StringValue.class).value;\n    }\n\n    /**\n     * get list String 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<String> listString() {\n        return this.getValue(StringValueList.class).values;\n    }\n\n    /**\n     * get int 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public int getInt() {\n        return this.getValue(IntValue.class).value;\n    }\n\n    /**\n     * get list int 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<Integer> listInt() {\n        return this.getValue(IntValueList.class).values;\n    }\n\n    /**\n     * get long 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public long getLong() {\n        return this.getValue(LongValue.class).value;\n    }\n\n    /**\n     * get list long 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<Long> listLong() {\n        return this.getValue(LongValueList.class).values;\n    }\n\n    /**\n     * get boolean 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public boolean getBoolean() {\n        return this.getValue(BoolValue.class).value;\n    }\n\n    /**\n     * get list boolean 业务数据。<a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">协议碎片</a>\n     *\n     * @return 业务数据\n     */\n    public List<Boolean> listBoolean() {\n        return this.getValue(BoolValueList.class).values;\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/collect/ResponseCollectMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.collect;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * 模块之间的访问，访问【同类型】的多个逻辑服\n * <pre>\n *     如： 模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据\n *     是把多个相同逻辑服结果聚合在一起\n *\n *     具体的意思可以参考文档中的说明：\n *     <a href=\"https://iohao.github.io/game/docs/communication/request_multiple_response\">request_multiple_response</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-22\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ResponseCollectMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -2510491415150451094L;\n\n    List<ResponseCollectItemMessage> messageList;\n    /**\n     * 响应码: 0:成功, 其他表示有错误\n     * <pre>\n     *     通常情况下，如果这里有错误码，基本是没有请求对应的逻辑服\n     * </pre>\n     */\n    int statusCode;\n    /** 错误信息 */\n    String statusMes;\n\n    public ResponseCollectMessage setError(MsgExceptionInfo msgExceptionInfo) {\n        this.statusCode = msgExceptionInfo.getCode();\n        this.statusMes = msgExceptionInfo.getMsg();\n        return this;\n    }\n\n    public boolean success() {\n        return statusCode == 0;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/collect/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 内部协议 - <a href=\"https://iohao.github.io/game/docs/communication/request_multiple_response\">请求同类型多个逻辑服通信结果</a>\n * <p>\n * 场景介绍\n * <pre>\n *     模块之间的访问，访问【同类型】的多个逻辑服，如： 模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据，这里的模块指的是逻辑服。\n *     假设启动了多个模块B，分别是：模块B-1、模块B-2、模块B-3、模块B-4 等。\n *     框架支持访问【同类型】的多个逻辑服，并把多个相同逻辑服结果收集到一起。\n * </pre>\n * <p>\n * 场景举例\n * <pre>\n *     【象棋逻辑服】有 3 台，分别是：《象棋逻辑服-1》、《象棋逻辑服-2》、《象棋逻辑服-3》，这些逻辑服可以在不同的进程中。\n *     我们可以在大厅逻辑服中向【同类型】的多个游戏逻辑服请求，意思是大厅发起一个向这 3 台象棋逻辑服的请求，框架会收集这 3 个结果集（假设结果是：当前服务器房间数）。\n *     当大厅得到这个结果集，可以统计房间的总数，又或者说根据这些信息做一些其他的业务逻辑；这里只是举个例子，实际当中可以发挥大伙的想象力。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-08-07\n */\npackage com.iohao.game.action.skeleton.protocol.collect;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/RequestCollectExternalMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.external;\n\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 游戏逻辑服访问游戏对外服，同时访问多个游戏对外服 - 请求\n * <pre>\n *     游戏逻辑服访问游戏对外服，因为只有游戏对外服持有这些数据\n *     把多个游戏对外服的结果聚合在一起\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class RequestCollectExternalMessage implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = -1661393033598374514L;\n\n    /**\n     * 业务码\n     * <pre>\n     *     游戏开发者从正数开始使用\n     *     框架会使用负数\n     *\n     *     游戏开发者可以通过自定义业务码，来获取一些对外服的业务数据，方便进行一些特殊的业务\n     * </pre>\n     */\n    int bizCode;\n    /** 发起请求的 userId */\n    long userId;\n    /** 请求业务数据 */\n    Serializable data;\n\n    /**\n     * 游戏对外服 idHash\n     * <pre>\n     *     当 sourceClientId == 0 时，将访问【所有的】游戏对外服。\n     *     当 sourceClientId != 0 时，将访问【指定的】游戏对外服。\n     *\n     *     为方便记忆，与 {@link HeadMetadata#setSourceClientId(int)} 同名\n     * </pre>\n     */\n    int sourceClientId;\n    /** traceId MDC */\n    String traceId;\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getData() {\n        return (T) data;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/ResponseCollectExternalItemMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.external;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * @author 渔民小镇\n * @date 2022-07-27\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ResponseCollectExternalItemMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 8687159017486669115L;\n\n    /** 由框架赋值 */\n    String logicServerId;\n    /** 响应码: 0:成功, 其他表示有错误 */\n    int responseStatus;\n    /** 错误消息 */\n    String errorMsg;\n    /** 响应数据，通常是（游戏对外服提供） */\n    Serializable data;\n\n    public boolean success() {\n        return responseStatus == 0;\n    }\n\n    public void setError(MsgExceptionInfo msgExceptionInfo) {\n        this.responseStatus = msgExceptionInfo.getCode();\n        this.errorMsg = msgExceptionInfo.getMsg();\n    }\n\n    public void setError(MsgException msgException) {\n        this.responseStatus = msgException.getMsgCode();\n        this.errorMsg = msgException.getMessage();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getData() {\n        return (T) data;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/ResponseCollectExternalMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.external;\n\nimport com.iohao.game.common.kit.CollKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * 游戏逻辑服访问游戏对外服，同时访问多个游戏对外服 - 响应\n * <pre>\n *     游戏逻辑服访问游戏对外服，因为只有游戏对外服持有这些数据\n *     把多个游戏对外服的结果聚合在一起\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ResponseCollectExternalMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 44170975617598505L;\n    /** 由于是调用多个游戏对外服，每条数据来自游戏对外服 */\n    List<ResponseCollectExternalItemMessage> messageList;\n\n    /**\n     * 是否有成功的\n     *\n     * @return true 表示在 item 里，其中一个是成功的\n     */\n    public boolean anySuccess() {\n\n        if (Objects.isNull(this.messageList)) {\n            return false;\n        }\n\n        for (ResponseCollectExternalItemMessage itemMessage : this.messageList) {\n            if (itemMessage.success()) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * 获取一个没有错误码的 ResponseCollectExternalItemMessage\n     *\n     * @return ResponseCollectExternalItemMessage\n     */\n    public Optional<ResponseCollectExternalItemMessage> optionalAnySuccess() {\n        if (CollKit.isEmpty(messageList)) {\n            return Optional.empty();\n        }\n\n        return this.messageList.stream()\n                .filter(ResponseCollectExternalItemMessage::success)\n                .findAny();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 内部协议 - <a href=\"https://iohao.github.io/game/docs/communication/external_biz_region\">访问游戏对外服与扩展</a>\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\npackage com.iohao.game.action.skeleton.protocol.external;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/login/SettingUserIdMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.login;\n\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * Set the userId to the gameExternalServer\n *\n * @author 渔民小镇\n * @date 2022-01-18\n */\n@Getter\n@FieldDefaults(makeFinal = true)\npublic final class SettingUserIdMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -7385687951893601229L;\n    /** userId */\n    long userId;\n\n    HeadMetadata headMetadata;\n\n    private SettingUserIdMessage(long userId, HeadMetadata headMetadata) {\n        this.userId = userId;\n        this.headMetadata = headMetadata;\n    }\n\n    public static SettingUserIdMessage of(long userId, HeadMetadata headMetadata) {\n        return new SettingUserIdMessage(userId, headMetadata);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/login/SettingUserIdMessageResponse.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.login;\n\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 设置 userId 的响应\n *\n * @author 渔民小镇\n * @date 2022-01-19\n */\n@Data\n@Accessors(chain = true)\npublic final class SettingUserIdMessageResponse implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -3776596417948970990L;\n    /** true: userId 设置成功 */\n    boolean success;\n    /** 变更后的 userId */\n    long userId;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/login/SettingUserIdResult.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.login;\n\nimport com.iohao.game.common.kit.exception.CommonRuntimeException;\n\n/**\n * SettingUserIdResult\n *\n * @param success   true: login successful\n * @param exception If success is true, the exception is null.\n * @author 渔民小镇\n * @date 2024-10-18\n * @since 21.19\n */\npublic record SettingUserIdResult(boolean success, Exception exception) {\n\n    public static final SettingUserIdResult SUCCESS = new SettingUserIdResult(true, null);\n\n    public static SettingUserIdResult ofError(Exception exception) {\n        return new SettingUserIdResult(false, exception);\n    }\n\n    public static SettingUserIdResult ofError(String message) {\n        return ofError(new CommonRuntimeException(message));\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 内部协议\n *\n * @author 渔民小镇\n * @date 2024-08-07\n */\npackage com.iohao.game.action.skeleton.protocol;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/processor/EndPointLogicServerMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.processor;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * 玩家绑定逻辑服\n *\n * @author 渔民小镇\n * @date 2022-05-28\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class EndPointLogicServerMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -281565377520818401L;\n\n    /** 用户 id */\n    List<Long> userList;\n\n    /** 需要绑定的多个游戏逻辑服 id */\n    Set<String> logicServerIdSet;\n\n    EndPointOperationEnum operation;\n\n    /**\n     * 添加需要绑定的游戏逻辑服 id\n     *\n     * @param logicServerId 需要绑定的游戏逻辑服 id\n     * @return this\n     */\n    public EndPointLogicServerMessage addLogicServerId(String logicServerId) {\n        Objects.requireNonNull(logicServerId);\n        this.initSet();\n        this.logicServerIdSet.add(logicServerId);\n        return this;\n    }\n\n    /**\n     * 添加需要绑定的游戏逻辑服 id\n     * <p>\n     * 使用示例\n     * <pre>\n     *     new EndPointLogicServerMessage().addLogicServerId(Set.of(\"1-1\", \"5-1\"));\n     * </pre>\n     *\n     * @param idSet 需要绑定的游戏逻辑服 id Set\n     * @return this\n     */\n    public EndPointLogicServerMessage addLogicServerId(Set<String> idSet) {\n        Objects.requireNonNull(idSet);\n        this.initSet();\n        this.logicServerIdSet.addAll(idSet);\n        return this;\n    }\n\n    private void initSet() {\n        if (Objects.isNull(this.logicServerIdSet)) {\n            this.logicServerIdSet = new HashSet<>();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/processor/EndPointOperationEnum.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.processor;\n\n/**\n * 游戏逻辑服动态绑定枚举\n *\n * @author 渔民小镇\n * @date 2023-06-07\n */\npublic enum EndPointOperationEnum {\n    /**\n     * 追加绑定游戏逻辑服\n     * <p>\n     * 举例：\n     * <pre>\n     *     在追加之前，如果玩家已经绑定了 [1-1] 的游戏逻辑服 id；\n     *\n     *     现在玩家添加 [2-2、3-1] 的游戏逻辑服 id；\n     *\n     *     此时玩家所绑定的游戏逻辑服数据为 [1-1、2-2、3-1]，一共 3 条数据\n     * </pre>\n     */\n    APPEND_BINDING,\n    /**\n     * 覆盖绑定游戏逻辑服\n     * <p>\n     * 举例：\n     * <pre>\n     *     在覆盖之前，如果玩家已经绑定了 [1-1] 的游戏逻辑服 id；\n     *\n     *     现在玩家添加 [2-2、3-1] 的游戏逻辑服 id；\n     *\n     *     此时玩家所绑定的游戏逻辑服数据为 [2-2、3-1]，一共 2 条数据，新的覆盖旧的；\n     *     无论之前有没有绑定的数据，都将使用当前设置的值；\n     * </pre>\n     */\n    COVER_BINDING,\n    /**\n     * 移除绑定的游戏逻辑服\n     * <p>\n     * 举例：\n     * <pre>\n     *     在移除之前，如果玩家已经绑定了 [1-1、2-2、3-1] 的游戏逻辑服 id；\n     *\n     *     现在玩家添加 [2-2、3-1] 为需要移除的游戏逻辑服 id；\n     *\n     *     此时玩家所绑定的游戏逻辑服数据为 [1-1]，一共 1 条数据，被移除了 2 条数据；\n     * </pre>\n     */\n    REMOVE_BINDING,\n    /**\n     * 清除所有绑定的游戏逻辑服\n     */\n    CLEAR\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/processor/SimpleServerInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.processor;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 一些简单的服务器信息\n *\n * @author 渔民小镇\n * @date 2023-04-23\n */\n@Getter\n@Setter\n@ToString\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class SimpleServerInfo implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -4231013083939730244L;\n\n    /** 服务器唯一标识 */\n    String id;\n    /**\n     * 逻辑服标签 （tag 相当于归类）\n     * <pre>\n     *     用于逻辑服的归类\n     *     假设逻辑服： 战斗逻辑服 启动了两台或以上，为了得到启动连接的逻辑服，我们可以通过 tag 在后台查找\n     *     相同的逻辑服一定要用相同的 tag\n     * </pre>\n     */\n    String tag;\n    /** 模块名 */\n    String name;\n    /** 服务器唯一标识 hash */\n    int idHash;\n    /**\n     * 逻辑服类型\n     */\n    String brokerClientType;\n    /** 启动时间 */\n    long startTime;\n    /** 逻辑服状态 */\n    int status;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/processor/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 内部协议 - <a href=\"https://iohao.github.io/game/docs/manual/binding_logic_server\">玩家动态绑定游戏逻辑服</a>\n * <p>\n * 简介\n * <pre>\n *     动态绑定游戏逻辑服，指的是玩家与游戏逻辑服绑定后，之后的请求都由该游戏逻辑服来处理。\n *     玩家动态绑定逻辑服节点后，之后的请求都由这个绑定的游戏逻辑服来处理，可以实现类似 LOL、王者荣耀匹配后动态分配房间的效果。\n *     支持玩家与多个游戏逻辑服的动态绑定。\n * </pre>\n * <p>\n * 使用场景\n * <pre>\n *     跨服活动、跨服战斗等。\n *     动态绑定游戏逻辑服可以解决玩家增量的问题，我们都知道一台机器所能承载的运算是有上限的；当上限达到时，就需要增加新机器来分摊请求量；如果你开发的游戏是有状态的，那么你如何解决请求分配的问题呢？在比如让你做一个类似 LOL、王者荣耀的匹配，将匹配好的玩家分配到一个房间中，之后这些玩家的请求都能在同一个游戏逻辑服上处理，这种业务你该如何实现呢？\n *     使用框架提供的动态绑定逻辑服节点可以轻松解决此类问题，而且还可以根据业务规则，计算出当前空闲最多的游戏逻辑服，并将此游戏逻辑服与玩家做绑定，从而做到均衡的利用机器资源，来防止请求倾斜的问题。\n * </pre>\n * <p>\n * for example\n * <pre>{@code\n * // 绑定消息\n * EndPointLogicServerMessage endPointLogicServerMessage = new EndPointLogicServerMessage()\n *         // 需要绑定的玩家，示例中只取了当前请求匹配的玩家\n *         .setUserList(userIdList)\n *         // 添加需要绑定的逻辑服id，下面绑定了两个；\n *         // 1.给绑定一个房间游戏逻辑服的 id\n *         // 2.绑定 animal 游戏逻辑服就简单点，固定写 id 为 2-1 的；\n *         .addLogicServerId(logicServerId)\n *         .addLogicServerId(\"2-1\")\n *         // 覆盖绑定游戏逻辑服\n *         .setOperation(EndPointOperationEnum.COVER_BINDING);\n *\n * // 发送消息到网关\n * ProcessorContext processorContext = BrokerClientHelper.getProcessorContext();\n * processorContext.invokeOneway(endPointLogicServerMessage);\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2024-08-07\n */\npackage com.iohao.game.action.skeleton.protocol.processor;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/BoolValue.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Ignore;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.core.codec.DataSelfEncode;\nimport lombok.ToString;\n\nimport java.util.Objects;\n\n/**\n * boolean value\n *\n * @author 渔民小镇\n * @date 2023-02-07\n */\n@ToString\n@ProtobufClass\npublic final class BoolValue implements DataSelfEncode {\n    /** bool 值 */\n    @Protobuf(fieldType = FieldType.BOOL, order = 1)\n    public boolean value;\n\n    transient byte[] data;\n\n    @Ignore\n    private static final BoolValue trueValue = create(true);\n    @Ignore\n    private static final BoolValue falseValue = create(false);\n\n    public static BoolValue of(boolean value) {\n        return value ? trueValue : falseValue;\n    }\n\n    public static BoolValue ofTrue() {\n        return trueValue;\n    }\n\n    public static BoolValue ofFalse() {\n        return falseValue;\n    }\n\n    private static BoolValue create(Boolean value) {\n        var theValue = new BoolValue();\n        theValue.value = value;\n        theValue.data = DataCodecKit.encode(theValue);\n        return theValue;\n    }\n\n    @Override\n    public byte[] encodeData() {\n        return Objects.nonNull(data) ? data : DataCodecKit.encode(this);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/BoolValueList.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.ToString;\n\nimport java.util.List;\n\n/**\n * boolean list\n *\n * @author 渔民小镇\n * @date 2023-02-07\n */\n@ToString\n@ProtobufClass\npublic final class BoolValueList {\n    /** boolList */\n    @Protobuf(fieldType = FieldType.BOOL, order = 1)\n    public List<Boolean> values;\n\n    public static BoolValueList of(List<Boolean> values) {\n        var theValue = new BoolValueList();\n        theValue.values = values;\n        return theValue;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/ByteValueList.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.common.kit.CollKit;\nimport lombok.ToString;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * object\n *\n * @author 渔民小镇\n * @date 2023-04-17\n */\n@ToString\n@ProtobufClass\npublic final class ByteValueList {\n    /** byte[] List */\n    @Protobuf(fieldType = FieldType.BYTES, order = 1)\n    public List<byte[]> values;\n\n    public static ByteValueList of(List<byte[]> values) {\n        if (CollKit.isEmpty(values)) {\n            return new ByteValueList();\n        }\n\n        var theValue = new ByteValueList();\n        theValue.values = values;\n        return theValue;\n    }\n\n    public static <T> ByteValueList ofList(Collection<T> values) {\n        if (CollKit.isEmpty(values)) {\n            return new ByteValueList();\n        }\n\n        return of(values.stream().map(DataCodecKit::encode).toList());\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/IntValue.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.ToString;\n\n/**\n * int value\n *\n * @author 渔民小镇\n * @date 2023-02-10\n */\n@ToString\n@ProtobufClass\npublic final class IntValue {\n    /** int 值 */\n    @Protobuf(fieldType = FieldType.SINT32, order = 1)\n    public int value;\n\n    public static IntValue of(int value) {\n        var theValue = new IntValue();\n        theValue.value = value;\n        return theValue;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/IntValueList.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.ToString;\n\nimport java.util.List;\n\n/**\n * int list\n *\n * @author 渔民小镇\n * @date 2023-02-10\n */\n@ToString\n@ProtobufClass\npublic final class IntValueList {\n    /** intList */\n    @Protobuf(fieldType = FieldType.SINT32, order = 1)\n    public List<Integer> values;\n\n    public static IntValueList of(List<Integer> values) {\n        var theValue = new IntValueList();\n        theValue.values = values;\n        return theValue;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/LongValue.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * long value\n *\n * @author 渔民小镇\n * @date 2023-02-10\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic final class LongValue {\n    /** long 值 */\n    @Protobuf(fieldType = FieldType.SINT64, order = 1)\n    public long value;\n\n    public static LongValue of(long value) {\n        var theValue = new LongValue();\n        theValue.value = value;\n        return theValue;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/LongValueList.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\n\n/**\n * long list\n *\n * @author 渔民小镇\n * @date 2023-02-10\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic final class LongValueList {\n    /** longList */\n    @Protobuf(fieldType = FieldType.SINT64, order = 1)\n    public List<Long> values;\n\n    public static LongValueList of(List<Long> values) {\n        var theValue = new LongValueList();\n        theValue.values = values;\n        return theValue;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/StringValue.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.ToString;\n\n/**\n * string value\n *\n * @author 渔民小镇\n * @date 2023-02-05\n */\n@ToString\n@ProtobufClass\npublic final class StringValue {\n    /** string value */\n    @Protobuf(fieldType = FieldType.STRING, order = 1)\n    public String value;\n\n    public static StringValue of(String value) {\n        var theValue = new StringValue();\n        theValue.value = value;\n        return theValue;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/StringValueList.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.ToString;\n\nimport java.util.List;\n\n/**\n * string list\n *\n * @author 渔民小镇\n * @date 2023-02-05\n */\n@ToString\n@ProtobufClass\npublic final class StringValueList {\n    /** string list */\n    @Protobuf(fieldType = FieldType.STRING, order = 1)\n    public List<String> values;\n\n    public static StringValueList of(List<String> values) {\n        var theValue = new StringValueList();\n        theValue.values = values;\n        return theValue;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/ValueRecord.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport lombok.Getter;\n\n/**\n * Wrapper values contain single and list classes\n *\n * @author 渔民小镇\n * @date 2024-11-01\n * @since 21.20\n */\n@Getter\npublic final class ValueRecord {\n    final Class<?> valueClazz;\n    final Class<?> valueListClazz;\n\n    ValueRecord(Class<?> valueClazz, Class<?> valueListClazz) {\n        this.valueClazz = valueClazz;\n        this.valueListClazz = valueListClazz;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.*;\n\n/**\n * 装箱、拆箱包装工具\n * <a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">解决协议碎片</a>\n *\n * @author 渔民小镇\n * @date 2023-06-09\n */\n@UtilityClass\npublic class WrapperKit {\n    public IntValue of(int value) {\n        return IntValue.of(value);\n    }\n\n    public IntValueList ofListIntValue(List<Integer> values) {\n        return IntValueList.of(values);\n    }\n\n    public BoolValue of(boolean value) {\n        return BoolValue.of(value);\n    }\n\n    public BoolValueList ofListBoolValue(List<Boolean> values) {\n        return BoolValueList.of(values);\n    }\n\n    public LongValue of(long value) {\n        return LongValue.of(value);\n    }\n\n    public LongValueList ofListLongValue(List<Long> values) {\n        return LongValueList.of(values);\n    }\n\n    public StringValue of(String value) {\n        return StringValue.of(value);\n    }\n\n    public StringValueList ofListStringValue(List<String> values) {\n        return StringValueList.of(values);\n    }\n\n    public <T> ByteValueList ofListByteValue(List<T> values) {\n        return ofList(values);\n    }\n\n    public <T> ByteValueList ofList(List<T> values) {\n        return ByteValueList.ofList(values);\n    }\n\n    public <T> ByteValueList ofListByteValue(Collection<T> values) {\n        return ByteValueList.ofList(values);\n    }\n\n    /** 框架支持的协议碎片类型 */\n    final Set<Class<?>> wrapperTypeSet = Set.of(\n            int.class,\n            Integer.class,\n            IntValue.class,\n\n            long.class,\n            Long.class,\n            LongValue.class,\n\n            boolean.class,\n            Boolean.class,\n            BoolValue.class,\n\n            String.class,\n            StringValue.class\n    );\n\n    /**\n     * 框架支持的协议碎片类型\n     *\n     * @param clazz class\n     * @return true 是框架支持的协议碎片类型\n     * @since 21.7\n     */\n    public boolean isWrapper(Class<?> clazz) {\n        return wrapperTypeSet.contains(clazz);\n    }\n\n    final Map<Class<?>, ValueRecord> refTypeMap = new HashMap<>();\n\n    static {\n        var intRecord = new ValueRecord(IntValue.class, IntValueList.class);\n        refTypeMap.put(int.class, intRecord);\n        refTypeMap.put(Integer.class, intRecord);\n        refTypeMap.put(IntValue.class, intRecord);\n\n        var longRecord = new ValueRecord(LongValue.class, LongValueList.class);\n        refTypeMap.put(long.class, longRecord);\n        refTypeMap.put(Long.class, longRecord);\n        refTypeMap.put(LongValue.class, longRecord);\n\n        var boolRecord = new ValueRecord(BoolValue.class, BoolValueList.class);\n        refTypeMap.put(boolean.class, boolRecord);\n        refTypeMap.put(Boolean.class, boolRecord);\n        refTypeMap.put(BoolValue.class, boolRecord);\n\n        var stringRecord = new ValueRecord(StringValue.class, StringValueList.class);\n        refTypeMap.put(String.class, stringRecord);\n        refTypeMap.put(StringValue.class, stringRecord);\n    }\n\n    @Deprecated\n    public Optional<Class<?>> optionalRefType(Class<?> clazz) {\n        return Optional.ofNullable(refTypeMap.get(clazz)).map(ValueRecord::getClass);\n    }\n\n    public Optional<ValueRecord> optionalValueRecord(Class<?> clazz) {\n        return Optional.ofNullable(refTypeMap.get(clazz));\n    }\n\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 业务框架 - 内部协议 - <a href=\"https://iohao.github.io/game/docs/manual/protocol_fragment\">装箱、拆箱包装；解决协议碎片</a>\n * 主要解决两个问题：碎片协议、使用时可自动装箱和拆箱。\n * <p>\n * for example\n * <pre>{@code\n * // 可以方便的接收与返回基础类型的数据\n * @ActionController(6)\n * public class IntAction {\n *     @ActionMethod(10)\n *     public int int2int(int value) {\n *         return value + 1;\n *     }\n *\n *     @ActionMethod(12)\n *     public List<Integer> intList2intList(List<Integer> intList) {\n *         List<Integer> list = new ArrayList<>();\n *         list.add(1);\n *         list.add(2);\n *         return list;\n *     }\n *\n *     @ActionMethod(30)\n *     public String string2string(String s) {\n *         return s + 1;\n *     }\n *\n *     @ActionMethod(32)\n *     public List<String> stringList2stringList(List<String> stringList) {\n *         List<String> list = new ArrayList<>();\n *         list.add(11L + \"\");\n *         list.add(22L + \"\");\n *         return list;\n *     }\n * }\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2023-06-09\n */\npackage com.iohao.game.action.skeleton.protocol.wrapper;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/Pulses.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse;\n\nimport com.iohao.game.action.skeleton.pulse.core.consumer.DefaultPulseConsumers;\nimport com.iohao.game.action.skeleton.pulse.core.producer.DefaultPulseProducers;\nimport com.iohao.game.action.skeleton.pulse.core.consumer.PulseConsumers;\nimport com.iohao.game.action.skeleton.pulse.core.producer.PulseProducers;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 脉冲管理器\n *\n * @author 渔民小镇\n * @date 2023-04-21\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic final class Pulses {\n    /** 脉冲信号生产者 */\n    PulseConsumers pulseConsumers = new DefaultPulseConsumers();\n    /** 脉冲信号消费者 */\n    PulseProducers pulseProducers = new DefaultPulseProducers();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/PulseChannel.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core;\n\n/**\n * 脉冲信号频道\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic interface PulseChannel {\n    /**\n     * 信号频道接收器\n     *\n     * @return 信号频道\n     */\n    String channel();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/PulseTransmit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core;\n\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalMessage;\n\n/**\n * 脉冲消息发射器\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic interface PulseTransmit {\n    /**\n     * 发射脉冲信号消息\n     *\n     * @param message 脉冲信号消息\n     */\n    void transmit(PulseSignalMessage message);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/consumer/DefaultPulseConsumers.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.consumer;\n\nimport com.iohao.game.action.skeleton.pulse.core.PulseTransmit;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalResponse;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.io.Serializable;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-20\n */\n@Slf4j\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultPulseConsumers implements PulseConsumers {\n    final Map<String, PulseConsumer<?>> map = new NonBlockingHashMap<>();\n    PulseTransmit pulseTransmit;\n\n    public void addPulseConsumer(PulseConsumer<?> pulseConsumer) {\n        String channel = pulseConsumer.channel();\n        this.map.putIfAbsent(channel, pulseConsumer);\n    }\n\n    public void acceptPulseSign(PulseSignalRequest request) {\n        // 通过信号频道，找到对应的脉冲消费者来处理该信号\n        String channel = request.getChannel();\n        PulseConsumer<?> pulseConsumer = this.map.get(channel);\n        if (Objects.isNull(pulseConsumer)) {\n            return;\n        }\n\n        try {\n            // 把需要使用的变量先取出来，防止开发者变更 request 的内容。\n            int sourceClientId = request.getSourceClientId();\n            // 脉冲消费者处理后的数据\n            Serializable data = pulseConsumer.accept(request.getData(), request);\n            if (Objects.isNull(data)) {\n                // 如果为 null 表示不需要响应数据给脉冲发射器\n                return;\n            }\n\n            // 脉冲响应\n            PulseSignalResponse response = new PulseSignalResponse();\n            response.setChannel(channel);\n            response.setSourceClientId(sourceClientId);\n            response.setData(data);\n\n            // 脉冲消费者信号响应\n            pulseTransmit.transmit(response);\n        } catch (Throwable e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public void setPulseTransmit(PulseTransmit pulseTransmit) {\n        this.pulseTransmit = pulseTransmit;\n    }\n\n    @Override\n    public PulseTransmit getPulseTransmit() {\n        return this.pulseTransmit;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/consumer/PulseConsumer.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.consumer;\n\nimport java.io.Serializable;\n\n/**\n * 脉冲消费者\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic interface PulseConsumer<T extends Serializable> extends PulseSignalRequestAccept<T> {\n\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/consumer/PulseConsumers.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.consumer;\n\nimport com.iohao.game.action.skeleton.pulse.core.PulseTransmit;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\n\n/**\n * 脉冲消费器\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic interface PulseConsumers {\n    /**\n     * 添加脉冲信号消费者\n     *\n     * @param pulseConsumer 脉冲信号消费者\n     */\n    void addPulseConsumer(PulseConsumer<?> pulseConsumer);\n\n    /**\n     * 接收脉冲信号\n     * <pre>\n     *     当消费者接收到信号后\n     * </pre>\n     *\n     * @param request 脉冲信号请求\n     */\n    void acceptPulseSign(PulseSignalRequest request);\n\n    /**\n     * 设置脉冲消息发射器\n     *\n     * @param pulseTransmit 脉冲消息发射器\n     */\n    void setPulseTransmit(PulseTransmit pulseTransmit);\n\n    /**\n     * 得到脉冲消息发射器\n     *\n     * @return 脉冲消息发射器\n     */\n    PulseTransmit getPulseTransmit();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/consumer/PulseSignalRequestAccept.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.consumer;\n\nimport com.iohao.game.action.skeleton.pulse.core.PulseChannel;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\n\nimport java.io.Serializable;\n\n/**\n * 接收-脉冲信号请求\n *\n * @author 渔民小镇\n * @date 2023-04-23\n */\npublic interface PulseSignalRequestAccept<T> extends PulseChannel {\n    /**\n     * 接收脉冲生产者的业务消息\n     *\n     * @param message 业务消息\n     * @param request 脉冲信号响应\n     * @return 响应业务消息给脉冲生产者，如果为 null 表示不需要响应数据\n     */\n    Serializable accept(T message, PulseSignalRequest request);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/producer/DefaultPulseProducers.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.producer;\n\nimport com.iohao.game.action.skeleton.pulse.core.PulseTransmit;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalResponse;\nimport com.iohao.game.common.kit.ExecutorKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-20\n */\n@Slf4j\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultPulseProducers implements PulseProducers {\n    static final ScheduledExecutorService executor = ExecutorKit.newSingleScheduled(DefaultPulseProducers.class.getSimpleName());\n    final Map<String, PulseProducer<?>> map = new NonBlockingHashMap<>();\n    final List<PulseTask> taskList = new CopyOnWriteArrayList<>();\n    final AtomicBoolean atomicBoolean = new AtomicBoolean(false);\n    final Map<String, PulseSignalResponseAccept<?>> responseAcceptMap = new NonBlockingHashMap<>();\n\n    PulseTransmit pulseTransmit;\n\n    @Override\n    public void addResponseAccept(PulseSignalResponseAccept<?> responseAccept) {\n        String channel = responseAccept.channel();\n        this.responseAcceptMap.put(channel, responseAccept);\n    }\n\n    @Override\n    public void addPulseProducer(PulseProducer<?> pulseProducer) {\n        String channel = pulseProducer.channel();\n\n        PulseProducer<?> pulse = this.map.putIfAbsent(channel, pulseProducer);\n        if (Objects.nonNull(pulse)) {\n            return;\n        }\n\n        // 添加到任务中\n        this.taskList.add(new PulseTask(pulseProducer));\n    }\n\n    @Override\n    public void startup() {\n        if (atomicBoolean.compareAndSet(false, true)) {\n            final Consumer<PulseSignalRequest> consumer = request -> this.pulseTransmit.transmit(request);\n\n            final Runnable runnable = () -> taskList.forEach(task -> task.ifPresent(consumer));\n\n            DefaultPulseProducers.executor.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);\n        }\n    }\n\n    @Override\n    public void acceptPulseSign(PulseSignalResponse response) {\n        String channel = response.getChannel();\n\n        Optional.ofNullable(this.map.get(channel))\n                .ifPresent(pulseProducer -> pulseProducer.accept(response.getData(), response));\n\n        Optional.ofNullable(this.responseAcceptMap.get(channel))\n                .ifPresent(pulseProducer -> pulseProducer.accept(response.getData(), response));\n    }\n\n    @Override\n    public void setPulseTransmit(PulseTransmit pulseTransmit) {\n        this.pulseTransmit = pulseTransmit;\n    }\n\n    @Override\n    public PulseTransmit getPulseTransmit() {\n        return this.pulseTransmit;\n    }\n\n    private static class PulseTask {\n        final PulseProducer<?> pulseProducer;\n        int count;\n\n        PulseTask(PulseProducer<?> pulseProducer) {\n            this.pulseProducer = pulseProducer;\n            // 当为负数时也表示不想执行\n            this.count = pulseProducer.period();\n        }\n\n        void ifPresent(Consumer<PulseSignalRequest> consumer) {\n            this.count--;\n\n            if (this.count != 0) {\n                return;\n            }\n\n            try {\n                PulseSignalRequest request = this.pulseProducer.createMessage();\n                request.setChannel(this.pulseProducer.channel());\n\n                if (request.getSignalType() == 0) {\n                    String text = \"\"\"\n                            请添加脉冲信号类型 request.addSignalType(SignalType)\n                            class : {}\n                            request: {}\n                            \"\"\";\n\n                    log.error(text, this.pulseProducer.getClass().getSimpleName(), request);\n                    // 重置次数\n                    this.count = this.pulseProducer.period();\n                    return;\n                }\n\n                consumer.accept(request);\n            } catch (Throwable e) {\n                log.error(e.getMessage(), e);\n            }\n\n            // 重置次数\n            this.count = this.pulseProducer.period();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/producer/PulseCreatePeriod.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.producer;\n\n/**\n * 脉冲创建周期\n *\n * @author 渔民小镇\n * @date 2023-04-30\n */\npublic interface PulseCreatePeriod {\n\n    /**\n     * N 秒执行一次（N 秒发射一次脉冲信号）\n     * <pre>\n     *     return 120 ； 表示 120 秒执行一次\n     *\n     *     当为负数时也表示不想执行\n     * </pre>\n     *\n     * @return 秒\n     */\n    int period();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/producer/PulseCreateRequest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.producer;\n\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\n\n/**\n * 脉冲生产者的请求创建接口\n *\n * @author 渔民小镇\n * @date 2023-04-30\n */\npublic interface PulseCreateRequest {\n    /**\n     * 生产脉冲信号请求\n     *\n     * @return 脉冲信号请求\n     */\n    PulseSignalRequest createMessage();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/producer/PulseProducer.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.producer;\n\nimport java.io.Serializable;\n\n/**\n * 脉冲生产者\n * <pre>\n *     主要职责是：\n *     1 生产脉冲信号请求\n *     2 周期性的触发一个生产请求\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic interface PulseProducer<T extends Serializable> extends\n        PulseSignalResponseAccept<T>,\n        PulseCreatePeriod,\n        PulseCreateRequest {\n\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/producer/PulseProducers.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.producer;\n\nimport com.iohao.game.action.skeleton.pulse.core.PulseTransmit;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalResponse;\n\n/**\n * 脉冲生产器\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic interface PulseProducers {\n    /**\n     * 添加脉冲信号生产者\n     *\n     * @param pulseProducer 脉冲信号生产者\n     */\n    void addPulseProducer(PulseProducer<?> pulseProducer);\n\n    /**\n     * 添加脉冲信号响应接收\n     *\n     * @param responseAccept 脉冲信号响应接收\n     */\n    void addResponseAccept(PulseSignalResponseAccept<?> responseAccept);\n\n    /**\n     * 接收脉冲信号响应\n     *\n     * @param response 脉冲信号响应\n     */\n    void acceptPulseSign(PulseSignalResponse response);\n\n    /**\n     * 设置脉冲消息发射器\n     *\n     * @param pulseTransmit 脉冲消息发射器\n     */\n    void setPulseTransmit(PulseTransmit pulseTransmit);\n\n    /**\n     * 得到脉冲消息发射器\n     *\n     * @return 脉冲消息发射器\n     */\n    PulseTransmit getPulseTransmit();\n\n    /**\n     * 启动\n     */\n    void startup();\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/core/producer/PulseSignalResponseAccept.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.core.producer;\n\nimport com.iohao.game.action.skeleton.pulse.core.PulseChannel;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalResponse;\n\n/**\n * 接收-脉冲信号响应\n * <pre>\n *     脉冲生产者回调\n *     当接收到脉冲消费者的响应后，会使用此接口来接收响应数据\n * </pre>\n *\n * @param <T> 可以明确响应的具体业务数据类型\n * @author 渔民小镇\n * @date 2023-04-23\n */\n\npublic interface PulseSignalResponseAccept<T> extends PulseChannel {\n    /**\n     * 接收脉冲信号响应，由脉冲消费者响应的数据\n     *\n     * @param message  业务数据\n     * @param response 脉冲信号响应\n     */\n    void accept(T message, PulseSignalResponse response);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/message/EmptyMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.message;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 可用于占位\n *\n * @author 渔民小镇\n * @date 2023-04-30\n */\npublic class EmptyMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 6890608282265776947L;\n\n    static final EmptyMessage empty = new EmptyMessage();\n\n    public static EmptyMessage empty() {\n        return empty;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/message/PulseSignalMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.message;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 脉冲信号消息父类\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\n@Getter\n@Setter\n@ToString\n@FieldDefaults(level = AccessLevel.PROTECTED)\npublic sealed class PulseSignalMessage implements Serializable permits PulseSignalRequest, PulseSignalResponse {\n\n    @Serial\n    private static final long serialVersionUID = -954007335024894018L;\n\n    /**\n     * 信号类型\n     * see {@link SignalType}\n     */\n    int signalType;\n\n    /**\n     * 信号频道\n     * <pre>\n     *     不需要开发者设置，所在 PulseChannel 的 channel 值\n     * </pre>\n     */\n    String channel;\n\n    /** 业务数据 */\n    Serializable data;\n\n    /**\n     * 来源逻辑服 client id\n     * <pre>\n     *     see {@link com.iohao.game.common.kit.HashKit}\n     * </pre>\n     */\n    int sourceClientId;\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getData() {\n        return (T) data;\n    }\n\n    /**\n     * 信号类型添加\n     *\n     * @param signalType {@link SignalType}\n     */\n    public void addSignalType(int signalType) {\n        this.signalType |= signalType;\n    }\n\n    /**\n     * 信号类型是否存在\n     *\n     * @param signalType {@link SignalType}\n     * @return true 存在\n     */\n    public boolean containsSignalType(int signalType) {\n        return (this.signalType & signalType) == signalType;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/message/PulseSignalRequest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.message;\n\nimport java.io.Serial;\n\n/**\n * 脉冲信号 - 请求\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic final class PulseSignalRequest extends PulseSignalMessage {\n    @Serial\n    private static final long serialVersionUID = 5222065267195783170L;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/message/PulseSignalResponse.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.message;\n\nimport com.iohao.game.action.skeleton.protocol.processor.SimpleServerInfo;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.io.Serial;\n\n/**\n * 脉冲信号 - 响应\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\n@Setter\n@Getter\npublic final class PulseSignalResponse extends PulseSignalMessage {\n    @Serial\n    private static final long serialVersionUID = -5782262610772950867L;\n    SimpleServerInfo simpleServerInfo;\n\n    @Override\n    public String toString() {\n        return \"PulseSignalResponse{\" +\n                \"simpleServerInfo=\" + simpleServerInfo +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/message/SignalType.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.message;\n\n/**\n * 信号类型：目前只有游戏对外服、游戏逻辑服生效。\n * @author 渔民小镇\n * @date 2023-04-20\n */\npublic interface SignalType {\n    /** 游戏对外服 */\n    int external = 1;\n    /** Broker（游戏网关） -- 暂不支持 */\n    int broker = 2;\n    /** 游戏逻辑服 */\n    int logic = 4;\n    /** 游戏对外服 + Broker + logic */\n    int all = external | broker | logic;\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * 实验性模块：脉冲信号。\n * 注意，这是一个实验性模块，将来有可能会删除，开发者先不要使用。\n * <pre>\n *     脉冲信号模块：\n *     应用场景与发布订阅相似，但是发布评阅是一种无需反馈的方式，就是发布者发布消息后就不管之后的事情了。\n *\n *     而脉冲则比发布订阅多了接收反馈的动作，什么意思呢？\n *     脉冲生产者发送【脉冲信号请求】后，由脉冲消费者接收该请求。\n *     脉冲消费者接收请求方法执行后，会给脉冲生产者发送【脉冲信号响应】。而这一步也正是区别于发布订阅的关键所在。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\npackage com.iohao.game.action.skeleton.pulse;"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/runner/CreatePulsesRunner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.runner;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.SkeletonAttr;\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport com.iohao.game.action.skeleton.core.runner.Runner;\nimport com.iohao.game.action.skeleton.protocol.processor.SimpleServerInfo;\nimport com.iohao.game.action.skeleton.pulse.Pulses;\nimport com.iohao.game.action.skeleton.pulse.core.consumer.PulseConsumers;\nimport com.iohao.game.action.skeleton.pulse.core.producer.PulseProducers;\nimport com.iohao.game.action.skeleton.pulse.core.PulseTransmit;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalMessage;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalResponse;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-23\n */\npublic class CreatePulsesRunner implements Runner {\n\n    @Override\n    public void onStart(BarSkeleton skeleton) {\n        BrokerClientContext brokerClientContext = skeleton.option(SkeletonAttr.brokerClientContext);\n        Integer sourceClientId = skeleton.option(SkeletonAttr.logicServerIdHash);\n\n        // 脉冲管理器\n        Pulses pulses = getPulses(skeleton);\n\n        // 脉冲消费者\n        PulseConsumers pulseConsumers = pulses.getPulseConsumers();\n        pulseConsumers.setPulseTransmit(new ConsumerTransmit(brokerClientContext));\n        skeleton.option(SkeletonAttr.consumerPulseTransmit, pulseConsumers.getPulseTransmit());\n\n        // 脉冲生产者\n        PulseProducers pulseProducers = pulses.getPulseProducers();\n        pulseProducers.setPulseTransmit(new ProducerTransmit(brokerClientContext, sourceClientId));\n        skeleton.option(SkeletonAttr.producerPulseTransmit, pulseProducers.getPulseTransmit());\n\n        // 启动脉冲生产者\n        pulseProducers.startup();\n    }\n\n    protected Pulses getPulses(BarSkeleton skeleton) {\n        Pulses pulses = skeleton.option(SkeletonAttr.pulses);\n\n        if (Objects.isNull(pulses)) {\n            pulses = new Pulses();\n            // 将脉冲管理器的引用保存到业务框架的动态属性中\n            skeleton.option(SkeletonAttr.pulses, pulses);\n        }\n\n        return pulses;\n    }\n\n    record ConsumerTransmit(BrokerClientContext brokerClientContext) implements PulseTransmit {\n        @Override\n        public void transmit(PulseSignalMessage message) {\n\n            if (message instanceof PulseSignalResponse response) {\n                SimpleServerInfo simpleServerInfo = brokerClientContext.getSimpleServerInfo();\n                response.setSimpleServerInfo(simpleServerInfo);\n                this.brokerClientContext.sendResponse(message);\n            }\n\n        }\n    }\n\n    record ProducerTransmit(BrokerClientContext brokerClientContext, int sourceClientId) implements PulseTransmit {\n        @Override\n        public void transmit(PulseSignalMessage message) {\n            /*\n             * 设置当前逻辑服的的 id，目的是为了当有脉冲信号响应时，可以找到发送脉冲信号的服务器；\n             * 由于是当前逻辑服发送的脉冲信号，所以设置当前逻辑服 id。\n             *\n             * 将脉冲信号发射到 Broker（游戏网关）\n             */\n            if (message instanceof PulseSignalRequest request && request.getSignalType() != 0) {\n                message.setSourceClientId(sourceClientId);\n                this.brokerClientContext.sendResponse(message);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/pulse/runner/PulseRunner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.pulse.runner;\n\nimport com.iohao.game.action.skeleton.pulse.Pulses;\nimport com.iohao.game.action.skeleton.core.runner.Runner;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.SkeletonAttr;\nimport com.iohao.game.action.skeleton.pulse.core.consumer.PulseConsumers;\nimport com.iohao.game.action.skeleton.pulse.core.producer.PulseProducers;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-23\n */\npublic interface PulseRunner extends Runner {\n    /**\n     * 启动\n     * <pre>\n     *     框架会在系统启动后调用一次\n     * </pre>\n     *\n     * @param skeleton 业务框架\n     */\n    @Override\n    default void onStart(BarSkeleton skeleton) {\n        Pulses pulses = skeleton.option(SkeletonAttr.pulses);\n        PulseProducers pulseProducers = pulses.getPulseProducers();\n        PulseConsumers pulseConsumers = pulses.getPulseConsumers();\n        this.onStart(pulseProducers, pulseConsumers, skeleton);\n    }\n\n    /**\n     * 启动\n     * <pre>\n     *     框架会在系统启动后调用一次\n     * </pre>\n     *\n     * @param pulseProducers 脉冲生产器\n     * @param pulseConsumers 脉冲消费器\n     * @param skeleton       业务框架\n     */\n    void onStart(PulseProducers pulseProducers, PulseConsumers pulseConsumers, BarSkeleton skeleton);\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/BannerColorStrategy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport com.iohao.game.common.kit.RandomKit;\nimport org.fusesource.jansi.Ansi;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Stream;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\nfinal class BannerColorStrategy {\n\n    static Ansi.Color anyColor() {\n        List<Ansi.Color> collect = Stream.of(Ansi.Color.values())\n                // 不需要黑色\n                .filter(color -> color != Ansi.Color.BLUE)\n                .filter(color -> color != Ansi.Color.WHITE)\n                .filter(color -> color != Ansi.Color.BLACK)\n                .filter(color -> color != Ansi.Color.DEFAULT)\n                .toList();\n\n        return RandomKit.randomEle(collect);\n    }\n\n    UnaryOperator<String> anyColorFun() {\n        // 上色策略\n        UnaryOperator<String> colorNon = s -> s;\n        UnaryOperator<String> colorSingleFun = this::colorSingle;\n        UnaryOperator<String> colorRandomLineFun = this::colorRandomLine;\n        UnaryOperator<String> colorRandomFun = this::colorRandom;\n        UnaryOperator<String> colorRandomColumnFun = this::colorRandomColumn;\n        UnaryOperator<String> colorColumnFun = this::colorColumn;\n\n        List<UnaryOperator<String>> functionList = new ArrayList<>();\n        // 单色\n        functionList.add(colorNon);\n        functionList.add(colorSingleFun);\n\n        // 按列随机上色\n        functionList.add(colorRandomColumnFun);\n        // 随机列上色\n        functionList.add(colorColumnFun);\n        functionList.add(colorColumnFun);\n        // 随机行上色\n        functionList.add(colorRandomLineFun);\n        functionList.add(colorRandomLineFun);\n        functionList.add(colorRandomLineFun);\n        // 随机上色\n        functionList.add(colorRandomFun);\n        functionList.add(colorRandomFun);\n        functionList.add(colorRandomFun);\n\n        // 随机得到一个上色策略\n        return RandomKit.randomEle(functionList);\n    }\n\n    private String colorRandomLine(String banner) {\n        // 按行来上色\n        List<Ansi.Color> colorList = listColor();\n\n        Ansi ansi = Ansi.ansi();\n\n        char[] array = banner.toCharArray();\n\n        int anInt = RandomKit.randomInt(colorList.size());\n        Ansi.Color color = colorList.get(anInt);\n\n        for (char c : array) {\n            ansi.fg(color).a(c);\n\n            if (c == '\\n') {\n                color = RandomKit.randomEle(colorList);\n            }\n        }\n\n        return ansi.reset().toString();\n    }\n\n    private String colorSingle(String banner) {\n        // 上单色\n        Ansi.Color color = randomColor();\n        Ansi ansi = Ansi.ansi().fg(color).a(banner);\n        return ansi.reset().toString();\n    }\n\n    private String colorRandom(String banner) {\n        // 随机字符上色\n\n        List<Ansi.Color> colorList = listColor();\n\n        Ansi ansi = Ansi.ansi();\n        char[] array = banner.toCharArray();\n\n        for (char c : array) {\n            Ansi.Color color = RandomKit.randomEle(colorList);\n            ansi.fg(color).a(c);\n        }\n\n        return ansi.reset().toString();\n    }\n\n    private String colorRandomColumn(String banner) {\n        // 达到换行的字符数量\n        int widthLen = RandomKit.randomInt(1, 10);\n        Ansi.Color color = randomColor();\n\n        Ansi ansi = Ansi.ansi();\n\n        char[] array = banner.toCharArray();\n        for (int i = 0; i < array.length; i++) {\n            char c = array[i];\n\n            if (i % widthLen == 0) {\n                // 换色\n                color = randomColor();\n                widthLen = RandomKit.randomInt(1, 10);\n            }\n\n            ansi.fg(color).a(c);\n        }\n\n        return ansi.reset().toString();\n    }\n\n    private String colorColumn(String banner) {\n        Ansi ansi = Ansi.ansi();\n\n        TheColorColumn colorColumn = TheColorColumn.create();\n        List<TheColorColumn> list = new ArrayList<>();\n        list.add(colorColumn);\n\n        int lineNum = 0;\n\n        for (char c : banner.toCharArray()) {\n\n            if (c == '\\n') {\n                lineNum = 0;\n                colorColumn = list.get(lineNum);\n                colorColumn.reset();\n            }\n\n            if (!colorColumn.has()) {\n                lineNum++;\n                // 取下一个数据\n\n                if (lineNum >= list.size()) {\n                    // 增加一个颜色数据\n                    colorColumn = TheColorColumn.create();\n                    list.add(colorColumn);\n                }\n\n                colorColumn = list.get(lineNum);\n                colorColumn.reset();\n            }\n\n            colorColumn.render(ansi, c);\n\n\n        }\n\n        return ansi.reset().toString();\n    }\n\n    private static class TheColorColumn {\n        Ansi.Color color;\n        int widthLen;\n        int num;\n\n        TheColorColumn(Ansi.Color color, int widthLen) {\n            this.color = color;\n            this.widthLen = widthLen;\n            this.num = widthLen;\n        }\n\n        void reset() {\n            this.num = widthLen;\n        }\n\n        static TheColorColumn create() {\n            int widthLen = RandomKit.randomInt(1, 5);\n            Ansi.Color color = randomColor();\n            return new TheColorColumn(color, widthLen);\n        }\n\n        void render(Ansi ansi, char c) {\n            this.num--;\n            ansi.fg(color).a(c);\n        }\n\n        boolean has() {\n            return this.num > 0;\n        }\n    }\n\n    private List<Ansi.Color> listColor() {\n        return Stream.of(Ansi.Color.values())\n                // 不需要黑色\n                .filter(color -> color != Ansi.Color.BLACK)\n                .toList();\n    }\n\n    private static Ansi.Color randomColor() {\n        List<Ansi.Color> collect = Stream.of(Ansi.Color.values())\n                // 不需要黑色\n                .filter(color -> color != Ansi.Color.BLACK)\n                .toList();\n\n        return RandomKit.randomEle(collect);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/BannerData.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\nfinal class BannerData {\n\n    List<String> listData() {\n        List<String> bannerList = new ArrayList<>();\n\n        String banner;\n\n        banner = \"\"\"\n                d8b           .d8888b.                                 \n                Y8P          d88P  Y88b                                \n                             888    888                                \n                888  .d88b.  888         8888b.  88888b.d88b.   .d88b. \n                888 d88\"\"88b 888  88888     \"88b 888 \"888 \"88b d8P  Y8b\n                888 888  888 888    888 .d888888 888  888  888 88888888\n                888 Y88..88P Y88b  d88P 888  888 888  888  888 Y8b.    \n                888  \"Y88P\"   \"Y8888P88 \"Y888888 888  888  888  \"Y8888 \n                \"\"\";\n\n        bannerList.add(banner);\n\n        banner = \"\"\"\n                  ,,\n                  db             .g8\"\"\\\"bgd\n                               .dP'     `M\n                `7MM  ,pW\"Wq.  dM'       `  ,6\"Yb.  `7MMpMMMb.pMMMb.  .gP\"Ya\n                  MM 6W'   `Wb MM          8)   MM    MM    MM    MM ,M'   Yb\n                  MM 8M     M8 MM.    `7MMF',pm9MM    MM    MM    MM 8M\"\"\\\"\"\"\\\"\n                  MM YA.   ,A9 `Mb.     MM 8M   MM    MM    MM    MM YM.    ,\n                .JMML.`Ybmd9'    `\"bmmmdPY `Moo9^Yo..JMML  JMML  JMML.`Mbmmd'\n                \"\"\";\n\n\n        bannerList.add(banner);\n\n        banner = \"\"\"\n                ██╗ ██████╗  ██████╗  █████╗ ███╗   ███╗███████╗\n                ██║██╔═══██╗██╔════╝ ██╔══██╗████╗ ████║██╔════╝\n                ██║██║   ██║██║  ███╗███████║██╔████╔██║█████╗\n                ██║██║   ██║██║   ██║██╔══██║██║╚██╔╝██║██╔══╝\n                ██║╚██████╔╝╚██████╔╝██║  ██║██║ ╚═╝ ██║███████╗\n                ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝\n                \"\"\";\n\n        bannerList.add(banner);\n\n        banner = \"\"\"\n                 /$$            /$$$$$$                                  \\s\n                |__/           /$$__  $$                                 \\s\n                 /$$  /$$$$$$ | $$  \\\\__/  /$$$$$$  /$$$$$$/$$$$   /$$$$$$\\s\n                | $$ /$$__  $$| $$ /$$$$ |____  $$| $$_  $$_  $$ /$$__  $$\n                | $$| $$  \\\\ $$| $$|_  $$  /$$$$$$$| $$ \\\\ $$ \\\\ $$| $$$$$$$$\n                | $$| $$  | $$| $$  \\\\ $$ /$$__  $$| $$ | $$ | $$| $$_____/\n                | $$|  $$$$$$/|  $$$$$$/|  $$$$$$$| $$ | $$ | $$|  $$$$$$$\n                |__/ \\\\______/  \\\\______/  \\\\_______/|__/ |__/ |__/ \\\\_______/\n                \"\"\";\n        bannerList.add(banner);\n\n        banner = \"\"\"\n                .------..------..------..------..------..------.\n                |I.--. ||O.--. ||G.--. ||A.--. ||M.--. ||E.--. |\n                | (\\\\/) || :/\\\\: || :/\\\\: || (\\\\/) || (\\\\/) || (\\\\/) |\n                | :\\\\/: || :\\\\/: || :\\\\/: || :\\\\/: || :\\\\/: || :\\\\/: |\n                | '--'I|| '--'O|| '--'G|| '--'A|| '--'M|| '--'E|\n                `------'`------'`------'`------'`------'`------'\n                \"\"\";\n\n        bannerList.add(banner);\n\n        return bannerList;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/BreakingNewsAbout.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport com.iohao.game.common.kit.RandomKit;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\n\n/**\n * ioGame 启动新闻\n * <pre>\n *     名字来源 Michael Jackson 的歌曲 Breaking News\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-06-10\n */\n@UtilityClass\nclass BreakingNewsKit {\n\n    private BreakingNews breakingNews;\n\n    private BreakingNews getBreakingNews() {\n        if (breakingNews == null) {\n            if (Locale.getDefault().getLanguage().equals(\"zh\")) {\n                breakingNews = new DefaultBreakingNews();\n            } else {\n                breakingNews = new EnglishBreakingNews();\n            }\n        }\n\n        return breakingNews;\n    }\n\n    List<News> randomNewsList() {\n        List<News> news = getBreakingNews().listNews();\n        Collections.shuffle(news);\n\n        return news.stream().limit(2).toList();\n    }\n\n    News randomAdv() {\n        List<News> list = getBreakingNews().listAdv();\n        return RandomKit.randomEle(list);\n    }\n\n    News randomMainNews() {\n        var list = getBreakingNews().listMainNews();\n        return RandomKit.randomEle(list);\n    }\n}\n\nrecord News(String title, String url) {\n    @Override\n    public String toString() {\n        return String.format(\"%s - %s\", title, url);\n    }\n}\n\ninterface BreakingNews {\n    List<News> listMainNews();\n\n    List<News> listAdv();\n\n    List<News> listNews();\n}\n\nfinal class DefaultBreakingNews implements BreakingNews {\n\n    @Override\n    public List<News> listMainNews() {\n        return List.of(\n                new News(\"ioGame javadoc\", \"https://iohao.github.io/javadoc\"),\n                new News(\"ioGame issues\", \"https://github.com/iohao/ioGame/issues\"),\n                new News(\"ioGame 框架各版本更新日志\", \"https://iohao.github.io/game/docs/version_log\"),\n                new News(\"ioGame 发版本的频率：每月会发 1 ~ 2 个版本，通常在大版本内的升级总是兼容的\", \"\")\n        );\n    }\n\n    @Override\n    public List<News> listAdv() {\n        return List.of(\n                new News(\"在线体验 demo\", \"https://a.iohao.com\"),\n                new News(\"MMO\", \"https://iohao.github.io/game/docs/practices/mmo\"),\n                new News(\"桌游类、房间类的实战\", \"https://iohao.github.io/game/docs/practices/room_in_action\")\n        );\n    }\n\n    @Override\n    public List<News> listNews() {\n        List<News> list = new ArrayList<>();\n\n        list.add(new News(\"支持者名单\", \"https://iohao.github.io/game/docs/contribute/backers\"));\n\n        // 开发常见问题与小技巧\n        list.add(new News(\"全链路调用日志跟踪\", \"https://iohao.github.io/game/docs/manual/trace\"));\n\n        // 授权相关、实践类产品\n        list.add(new News(\"项目成本分析\", \"https://iohao.github.io/game/services/cost_analysis\"));\n\n        // 整体、架构相关\n        list.add(new News(\"架构简介\", \"https://iohao.github.io/game/docs/overall/architecture_intro\"));\n        list.add(new News(\"ioGame 架构灵活、部署多样性\", \"https://iohao.github.io/game/docs/overall/deploy_flexible\"));\n        list.add(new News(\"与传统架构的对比\", \"https://iohao.github.io/game/docs/overall/legacy_system\"));\n\n        list.add(new News(\"ioGame 请求的处理流程\", \"https://iohao.github.io/game/docs/overall/request_processing_procedure\"));\n        list.add(new News(\"ioGame 线程相关\", \"https://iohao.github.io/game/docs/overall/thread_executor\"));\n        list.add(new News(\"单服单进程、多服单进程、多服多进程的启动方式\", \"https://iohao.github.io/game/docs/manual/netty_run_one\"));\n        list.add(new News(\"代码组织与约定\", \"https://iohao.github.io/game/docs/manual_high/code_organization\"));\n        list.add(new News(\"同进程亲和性\", \"https://iohao.github.io/game/docs/manual_high/same_process\"));\n\n        // 游戏逻辑服\n        list.add(new News(\"游戏逻辑服 - 动态绑定游戏逻辑服\", \"https://iohao.github.io/game/docs/manual/binding_logic_server\"));\n        list.add(new News(\"游戏逻辑服 - 元信息、附加信息\", \"https://iohao.github.io/game/docs/manual/flowcontext_attachment\"));\n\n        // 游戏对外服\n        list.add(new News(\"游戏对外服 - 统一协议说明\", \"https://iohao.github.io/game/docs/manual_high/external_message\"));\n        list.add(new News(\"游戏对外服\", \"https://iohao.github.io/game/docs/overall/external_intro\"));\n        list.add(new News(\"游戏对外服 - 心跳设置与心跳钩子\", \"https://iohao.github.io/game/docs/external/idle\"));\n        list.add(new News(\"游戏对外服 - 用户上线、下线钩子\", \"https://iohao.github.io/game/docs/external/user_hook\"));\n        list.add(new News(\"游戏对外服 - 路由访问权限控制\", \"https://iohao.github.io/game/docs/external/access_authentication\"));\n        list.add(new News(\"游戏对外服 - 游戏对外服缓存\", \"https://iohao.github.io/game/docs/external/cache\"));\n        list.add(new News(\"游戏对外服 - ws token 鉴权、校验\", \"https://iohao.github.io/game/docs/external/ws_verify\"));\n        list.add(new News(\"游戏对外服 - 内置与可选的 Handler\", \"https://iohao.github.io/game/docs/external/netty_handler\"));\n\n        // 通讯方式\n        list.add(new News(\"通讯方式 - 请求同类型多个逻辑服通信结果\", \"https://iohao.github.io/game/docs/communication/request_multiple_response\"));\n        list.add(new News(\"通讯方式 - 访问游戏对外服与扩展\", \"https://iohao.github.io/game/docs/communication/external_biz_region\"));\n        list.add(new News(\"通讯方式 - 分布式事件总线\", \"https://iohao.github.io/game/docs/communication/event_bus\"));\n\n        // 内置工具\n        list.add(new News(\"内置 Kit - TaskKit 是一个任务、时间、延时监听、超时监听...等相结合的一个工具模块\", \"https://iohao.github.io/game/docs/kit/task_kit\"));\n        list.add(new News(\"内置 Kit - 属性监听\", \"https://iohao.github.io/game/docs/kit/property_change_listener\"));\n        list.add(new News(\"内置 Kit - 轻量可控的延时任务\", \"https://iohao.github.io/game/docs/kit/delay_task\"));\n\n        // 扩展模块\n        list.add(new News(\"扩展模块 - 领域事件可解决多人同一业务的并发问题\", \"https://iohao.github.io/game/docs/extension_module/domain_event\"));\n        list.add(new News(\"扩展模块 - 压测&模拟客户端请求\", \"https://iohao.github.io/game/docs/extension_module/simulation_client\"));\n        list.add(new News(\"扩展模块 - room 桌游、房间类的扩展模块\", \"https://iohao.github.io/game/docs/extension_module/room\"));\n        list.add(new News(\"扩展模块 - sdk-generate-code\", \"https://iohao.github.io/game/docs/extension_module/generate_code\"));\n\n        // 插件相关\n        list.add(new News(\"业务框架 - 插件介绍\", \"https://iohao.github.io/game/docs/manual/plugin_intro\"));\n        list.add(new News(\"插件 - DebugInOut 插件\", \"https://iohao.github.io/game/docs/core_plugin/action_debug\"));\n        list.add(new News(\"插件 - action 调用统计插件\", \"https://iohao.github.io/game/docs/core_plugin/action_stat\"));\n        list.add(new News(\"插件 - 业务线程监控插件\", \"https://iohao.github.io/game/docs/core_plugin/action_thread_monitor\"));\n        list.add(new News(\"插件 - 各时间段调用统计插件\", \"https://iohao.github.io/game/docs/core_plugin/action_time_range\"));\n        list.add(new News(\"插件 - 全链路调用日志跟踪\", \"https://iohao.github.io/game/docs/core_plugin/action_trace\"));\n\n        // 业务框架\n        list.add(new News(\"业务框架 - 简介\", \"https://iohao.github.io/game/docs/core/framework\"));\n        list.add(new News(\"业务框架 - FlowContext\", \"https://iohao.github.io/game/docs/manual/flowcontext\"));\n        list.add(new News(\"业务框架 - 断言 + 异常机制 = 清晰简洁的代码\", \"https://iohao.github.io/game/docs/manual/assert_game_code\"));\n        list.add(new News(\"业务框架 - 开启 JSR380 验证规范\", \"https://iohao.github.io/game/docs/core/jsr380\"));\n        list.add(new News(\"业务框架 - 解决协议碎片\", \"https://iohao.github.io/game/docs/manual/protocol_fragment\"));\n\n        return list;\n    }\n}\n\nfinal class EnglishBreakingNews implements BreakingNews {\n\n    @Override\n    public List<News> listMainNews() {\n        return List.of(\n                new News(\"ioGame javadoc\", \"https://iohao.github.io/javadoc\"),\n                new News(\"ioGame issues\", \"https://github.com/iohao/ioGame/issues\"),\n                new News(\"ioGame version log\", \"https://iohao.github.io/game/docs/version_log\"),\n                new News(\"Releases: 1 to 2 versions are released every month, and upgrades within a major version are always compatible, such as 21.1 is upgraded to any higher version 21.x\", \"\")\n        );\n    }\n\n    @Override\n    public List<News> listAdv() {\n        return List.of(\n                new News(\"online demo\", \"https://a.iohao.com\"),\n                new News(\"MMO\", \"https://iohao.github.io/game/docs/practices/mmo\"),\n                new News(\"Room games in action\", \"https://iohao.github.io/game/docs/practices/room_in_action\")\n        );\n    }\n\n    @Override\n    public List<News> listNews() {\n        List<News> list = new ArrayList<>();\n\n        list.add(new News(\"Backers\", \"https://iohao.github.io/game/docs/contribute/backers\"));\n\n        // 开发常见问题与小技巧\n        list.add(new News(\"Full-link call log tracking\", \"https://iohao.github.io/game/docs/manual/trace\"));\n\n        // 授权相关、实践类产品\n        list.add(new News(\"Project cost analysis\", \"https://iohao.github.io/game/services/cost_analysis\"));\n\n        // 整体、架构相关\n        list.add(new News(\"ioGame - Architecture Introduction\", \"https://iohao.github.io/game/docs/overall/architecture_intro\"));\n        list.add(new News(\"ioGame - Architecture Diversity\", \"https://iohao.github.io/game/docs/overall/deploy_flexible\"));\n        list.add(new News(\"ioGame - Comparison with traditional architecture\", \"https://iohao.github.io/game/docs/overall/legacy_system\"));\n\n        list.add(new News(\"ioGame - Request Processing Flow\", \"https://iohao.github.io/game/docs/overall/request_processing_procedure\"));\n        list.add(new News(\"ioGame - Thread related\", \"https://iohao.github.io/game/docs/overall/thread_executor\"));\n        list.add(new News(\"Startup methods for single server single process, multiple servers single process, and multiple servers multiple processes\", \"https://iohao.github.io/game/docs/manual/netty_run_one\"));\n        list.add(new News(\"Code Organization and Conventions\", \"https://iohao.github.io/game/docs/manual_high/code_organization\"));\n        list.add(new News(\"Same-process affinity\", \"https://iohao.github.io/game/docs/manual_high/same_process\"));\n\n        // 游戏逻辑服\n        list.add(new News(\"GameLogicServer - Dynamically bind game logic server\", \"https://iohao.github.io/game/docs/manual/binding_logic_server\"));\n        list.add(new News(\"GameLogicServer - Meta information, additional information\", \"https://iohao.github.io/game/docs/manual/flowcontext_attachment\"));\n\n        // 游戏对外服\n        list.add(new News(\"ExternalServer - Unified Protocol Description\", \"https://iohao.github.io/game/docs/manual_high/external_message\"));\n        list.add(new News(\"ExternalServer\", \"https://iohao.github.io/game/docs/overall/external_intro\"));\n        list.add(new News(\"ExternalServer - Heartbeat settings and heartbeat hooks\", \"https://iohao.github.io/game/docs/external/idle\"));\n        list.add(new News(\"ExternalServer - User online and offline hooks\", \"https://iohao.github.io/game/docs/external/user_hook\"));\n        list.add(new News(\"ExternalServer - Routing access control\", \"https://iohao.github.io/game/docs/external/access_authentication\"));\n        list.add(new News(\"ExternalServer - Game cache for external servers\", \"https://iohao.github.io/game/docs/external/cache\"));\n        list.add(new News(\"ExternalServer - ws token Authentication and verification\", \"https://iohao.github.io/game/docs/external/ws_verify\"));\n        list.add(new News(\"ExternalServer - Built-in and optional Handler\", \"https://iohao.github.io/game/docs/external/netty_handler\"));\n\n        // 通讯方式\n        list.add(new News(\"Communication - Request the communication results of multiple logical servers of the same type\", \"https://iohao.github.io/game/docs/communication/request_multiple_response\"));\n        list.add(new News(\"Communication - Get game data and expansion for external servers\", \"https://iohao.github.io/game/docs/communication/external_biz_region\"));\n        list.add(new News(\"Communication - Distributed Event Bus\", \"https://iohao.github.io/game/docs/communication/event_bus\"));\n\n        // 内置工具\n        list.add(new News(\"Built-in Kit - TaskKit is a tool module that combines tasks, time, delay monitoring, timeout monitoring, etc.\", \"https://iohao.github.io/game/docs/kit/task_kit\"));\n        list.add(new News(\"Built-in Kit - Property monitoring\", \"https://iohao.github.io/game/docs/kit/property_change_listener\"));\n        list.add(new News(\"Built-in Kit - Lightweight and controllable delayed tasks\", \"https://iohao.github.io/game/docs/kit/delay_task\"));\n\n        // 扩展模块\n        list.add(new News(\"ExtendedModule - Domain Events - Can solve the concurrency problem of multiple people doing the same business\", \"https://iohao.github.io/game/docs/extension_module/domain_event\"));\n        list.add(new News(\"ExtendedModule - Stress testing & simulating client requests\", \"https://iohao.github.io/game/docs/extension_module/simulation_client\"));\n        list.add(new News(\"ExtendedModule - Room - Extension modules for board games and rooms\", \"https://iohao.github.io/game/docs/extension_module/room\"));\n        list.add(new News(\"ExtendedModule - sdk-generate-code\", \"https://iohao.github.io/game/docs/extension_module/room\"));\n\n        // 插件相关\n        list.add(new News(\"Business Framework - Plugin Introduction\", \"https://iohao.github.io/game/docs/manual/plugin_intro\"));\n        list.add(new News(\"Plugin - DebugInOut\", \"https://iohao.github.io/game/docs/core_plugin/action_debug\"));\n        list.add(new News(\"Plugin - Action call statistics\", \"https://iohao.github.io/game/docs/core_plugin/action_stat\"));\n        list.add(new News(\"Plugin - Business thread monitoring\", \"https://iohao.github.io/game/docs/core_plugin/action_thread_monitor\"));\n        list.add(new News(\"Plugin - Call statistics for each time period\", \"https://iohao.github.io/game/docs/core_plugin/action_time_range\"));\n        list.add(new News(\"Plugin - Full-link call log tracking\", \"https://iohao.github.io/game/docs/core_plugin/action_trace\"));\n\n        // 业务框架\n        list.add(new News(\"Business Framework - Introduction\", \"https://iohao.github.io/game/docs/core/framework\"));\n        list.add(new News(\"Business Framework - FlowContext\", \"https://iohao.github.io/game/docs/manual/flowcontext\"));\n        list.add(new News(\"Business Framework - Assertions + exceptions = clear and concise code\", \"https://iohao.github.io/game/docs/manual/assert_game_code\"));\n        list.add(new News(\"Business Framework - Enable JSR380 validation specification\", \"https://iohao.github.io/game/docs/core/jsr380\"));\n        list.add(new News(\"Business Framework - Resolving protocol fragmentation\", \"https://iohao.github.io/game/docs/manual/protocol_fragment\"));\n\n        return list;\n    }\n}"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/InternalMemory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\nfinal class InternalMemory {\n\n    Map<String, String> getMemoryMap() {\n        Runtime run = Runtime.getRuntime();\n\n        long totalMemory = run.totalMemory();\n        long freeMemory = run.freeMemory();\n        long used = totalMemory - freeMemory;\n\n        Map<String, String> map = new LinkedHashMap<>(3);\n        map.put(\"used\", format(used));\n        map.put(\"freeMemory\", format(freeMemory));\n        map.put(\"totalMemory\", format(totalMemory));\n\n        return map;\n    }\n\n    private String format(long value) {\n\n        var gb = 1024L * 1024L * 1024L;\n        if (value >= gb) {\n            return (Math.round((double) value / gb * 100.0D) / 100.0D) + \"GB\";\n        }\n\n        var mb = 1024L * 1024L;\n        if (value >= mb) {\n            return (Math.round((double) value / mb * 100.0D) / 100.0D) + \"MB\";\n        }\n\n        var kb = 1024L;\n        if (value >= kb) {\n            return (Math.round((double) value / kb * 100.0D) / 100.0D) + \"KB\";\n        }\n\n        return \"\";\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/IoGameBanner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport com.iohao.game.action.skeleton.IoGameVersion;\nimport com.iohao.game.common.kit.RandomKit;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.RuntimeMXBean;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static java.lang.System.out;\n\n/**\n * ioGame Banner ， 不提供关闭 Banner 的方法，让开发者含泪看完 Banner\n *\n * @author 渔民小镇\n * @date 2023-01-30\n */\n@Slf4j\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class IoGameBanner {\n    /** 特殊字段，开发者不要使用 */\n    public static String flag21;\n    public static boolean troublemaker;\n    public static int troubleCounter;\n\n    final AtomicBoolean trigger = new AtomicBoolean(false);\n    /** 特殊字段，开发者不要使用 */\n    AtomicInteger errorCount = new AtomicInteger(0);\n    Date startTime = new Date();\n\n    CountDownLatch countDownLatch = new CountDownLatch(1);\n\n    public static void render() {\n\n        if (me().trigger.get()) {\n            return;\n        }\n\n        // 只触发一次\n        if (!me().trigger.compareAndSet(false, true)) {\n            return;\n        }\n\n        me().renderBanner1();\n    }\n\n    public void initCountDownLatch(int num) {\n        this.countDownLatch = new CountDownLatch(num);\n    }\n\n    public void countDown() {\n        if (Objects.nonNull(this.countDownLatch)) {\n            this.countDownLatch.countDown();\n        }\n    }\n\n    private void incErrorCount() {\n        if (Objects.nonNull(errorCount)) {\n            errorCount.getAndIncrement();\n        }\n    }\n\n    private final AtomicBoolean print = new AtomicBoolean(false);\n\n    public void ofRuntimeException(String message) {\n        if (!print.get()) {\n            return;\n        }\n\n        incErrorCount();\n        ThrowKit.ofRuntimeException(message);\n        render();\n    }\n\n    public static void printLine() {\n        out.println();\n    }\n\n    public static void printMessage(String message) {\n        out.print(message);\n    }\n\n    public static void printMessage(Object message) {\n        out.print(message);\n    }\n\n    public static void println1(Object message) {\n        out.println(message);\n    }\n\n    public static void printlnMsg(String message) {\n        out.println(message);\n    }\n\n    public static void printlnMsg2(String message) {\n        printlnMsg(message);\n    }\n\n    private void renderBanner1() {\n        print.set(true);\n\n        Runnable runnable = () -> {\n\n            try {\n                if (Objects.nonNull(IoGameBanner.me().countDownLatch)) {\n                    boolean r = IoGameBanner.me().countDownLatch.await(5, TimeUnit.SECONDS);\n                    if (!r) {\n                        IoGameBanner.printlnMsg(\"countDownLatch await is false\");\n                    }\n                }\n            } catch (InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n\n            var table = new ToyTable();\n\n            // app\n            var ioGameRegion = table.getRegion(\"ioGame\");\n            ioGameRegion.putLine(\"pid\", getPid());\n            ioGameRegion.putLine(\"version\", IoGameVersion.VERSION);\n            ioGameRegion.putLine(\"document\", \"http://game.iohao.com\");\n\n            // 内存相关\n            var internalMemory = new InternalMemory();\n            var memory = table.getRegion(\"Memory\");\n            memory.putAll(internalMemory.getMemoryMap());\n\n            // 启动时间\n            extractedTime(table);\n\n            extractedPrint(table);\n\n            extractedIoGameJavadocApi();\n\n            extractedAdv();\n            // breaking news\n            extractedBreakingNews();\n\n            extractedErrorCount();\n\n            clean();\n\n            IoGameBanner.printLine();\n        };\n\n        TaskKit.execute(runnable);\n    }\n\n    private void clean() {\n        this.startTime = null;\n        this.countDownLatch = null;\n        flag21 = \"ioGame21 \";\n        errorCount = null;\n    }\n\n    private void extractedTime(ToyTable table) {\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\n        Date endTime = new Date();\n        var consumeTime = (endTime.getTime() - this.startTime.getTime()) / 1000f;\n        var consume = String.format(\"%.2f s\", consumeTime);\n\n        // 时间相关\n        ToyTableRegion other = table.getRegion(\"Time\");\n        other.putLine(\"start\", simpleDateFormat.format(this.startTime));\n        other.putLine(\"end\", simpleDateFormat.format(endTime));\n        other.putLine(\"consume\", consume);\n    }\n\n    private void extractedBreakingNews() {\n        // 每次展示 N 条小报\n        var newsList = BreakingNewsKit.randomNewsList();\n        for (News news : newsList) {\n            System.out.printf(\"| News     | %s%n\", news);\n        }\n\n        IoGameBanner.printlnMsg(\"+----------+--------------------------------------------------------------------------------------\");\n    }\n\n    private void extractedAdv() {\n        String s = BreakingNewsKit.randomAdv().toString();\n        String builder = \"| adv      | %s%n\";\n        System.out.printf(builder, s);\n        IoGameBanner.printlnMsg(\"+----------+--------------------------------------------------------------------------------------\");\n    }\n\n    private void extractedIoGameJavadocApi() {\n        String s = BreakingNewsKit.randomMainNews().toString();\n        String builder = \"|          | %s%n\";\n        System.out.printf(builder, s);\n        IoGameBanner.printlnMsg(\"+----------+--------------------------------------------------------------------------------------\");\n    }\n\n    private void extractedErrorCount() {\n        if (Objects.isNull(errorCount) || errorCount.get() == 0) {\n            return;\n        }\n\n        String builder = \"| Error    | error count : %s%n\";\n        System.out.printf(builder, errorCount.get());\n        IoGameBanner.printlnMsg(\"+----------+--------------------------------------------------------------------------------------\");\n    }\n\n    private void extractedPrint(ToyTable table) {\n        // 为了在控制台上显示得不那么的枯燥，这里使用随机的 banner 和随机上色策略\n        List<String> bannerList = new BannerData().listData();\n        String banner = RandomKit.randomEle(bannerList);\n\n        // 上色策略\n        var anyFunction = new BannerColorStrategy().anyColorFun();\n        String anyBanner = anyFunction.apply(banner);\n\n        IoGameBanner.printLine();\n        IoGameBanner.printlnMsg(anyBanner);\n        table.render();\n    }\n\n    private static String getPid() {\n        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();\n        String name = runtime.getName();\n\n        try {\n            return name.substring(0, name.indexOf('@'));\n        } catch (Exception e) {\n            return \"-1\";\n        }\n    }\n\n    private IoGameBanner() {\n        flag21 = \"ioGame..21..\";\n    }\n\n    public static IoGameBanner me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final IoGameBanner ME = new IoGameBanner();\n    }\n\n    public void init() {\n\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/ToyLine.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\nfinal class ToyLine {\n    String key;\n    String value;\n\n    ToyTableRegion region;\n    String prefix = \" \";\n    String suffix = \" \";\n    String fill = \" \";\n\n    String render() {\n        StringBuilder bodyBuilder = new StringBuilder();\n\n        // body line key\n        bodyBuilder.append(prefix);\n        bodyBuilder.append(key);\n\n        int appendNum = region.keyMaxLen - key.length();\n        append(bodyBuilder, fill, appendNum);\n\n        // body line key\n        String keyValueFix = \"|\";\n        bodyBuilder.append(keyValueFix);\n        bodyBuilder.append(prefix);\n        bodyBuilder.append(value);\n\n        appendNum = region.valueMaxLen - value.length() - keyValueFix.length();\n        append(bodyBuilder, fill, appendNum);\n\n        bodyBuilder.append(suffix);\n        return bodyBuilder.toString();\n    }\n\n    private void append(StringBuilder builder, String c, int num) {\n        builder.append(String.valueOf(c).repeat(Math.max(0, num + 1)));\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/ToyTable.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport org.fusesource.jansi.Ansi;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\nfinal class ToyTable {\n    Ansi.Color color = BannerColorStrategy.anyColor();\n    Map<String, ToyTableRegion> regionMap = new LinkedHashMap<>();\n    ToyTableRender tableRender;\n    int bodyMaxNum;\n\n    void render() {\n\n        this.bodyMaxNum = this.countRegionMaxLine();\n        this.tableRender = new ToyTableRender(bodyMaxNum);\n\n        int size = this.regionMap.size();\n        int eleNum = 0;\n\n        for (ToyTableRegion region : regionMap.values()) {\n            region.render();\n            eleNum++;\n\n            boolean lastEle = size == eleNum;\n            if (!lastEle) {\n                tableRender.next();\n            }\n        }\n\n        this.tableRender.render();\n    }\n\n    int countRegionMaxLine() {\n        int lineMax = 0;\n\n        for (ToyTableRegion region : this.regionMap.values()) {\n\n            int size = region.lineMap.size();\n            if (size > lineMax) {\n                lineMax = size;\n            }\n        }\n\n        return lineMax;\n    }\n\n    ToyTableRegion getRegion(String regionName) {\n\n        ToyTableRegion region = this.regionMap.get(regionName);\n\n        if (Objects.isNull(region)) {\n\n            region = new ToyTableRegion();\n            region.head = regionName;\n            region.table = this;\n\n            this.regionMap.putIfAbsent(regionName, region);\n            region = this.regionMap.get(regionName);\n        }\n\n        return region;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/ToyTableRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport org.fusesource.jansi.Ansi;\n\nimport java.util.*;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\nfinal class ToyTableRegion {\n    Map<String, ToyLine> lineMap = new LinkedHashMap<>();\n    String head;\n    String prefix = \" \";\n    String suffix = \" \";\n    String fill = \" \";\n    int valueMaxLen;\n    int keyMaxLen;\n    String separatorLine;\n    ToyTable table;\n\n    void putLine(String key, String value) {\n        ToyLine line = this.lineMap.get(key);\n\n        if (Objects.isNull(line)) {\n            line = new ToyLine();\n            line.key = key;\n            line.value = value;\n            line.region = this;\n\n            this.lineMap.putIfAbsent(key, line);\n            line = this.lineMap.get(key);\n\n            this.counter(line);\n        }\n    }\n\n    void putAll(Map<?, ?> map) {\n        for (Map.Entry<?, ?> entry : map.entrySet()) {\n            this.putLine(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));\n        }\n    }\n\n    void render() {\n        var tableRender = table.tableRender;\n        // 线条\n        tableRender.line(keyMaxLen, valueMaxLen);\n\n        // 表头\n        String renderHead = this.renderHead();\n        tableRender.headContent.append(renderHead);\n\n        // body\n        int num = 0;\n\n        for (ToyLine line : lineMap.values()) {\n            var bodyBuilder = tableRender.bodyContentList.get(num);\n            num++;\n\n            String lineRender = line.render();\n            bodyBuilder.append(lineRender);\n        }\n\n        int bodyMaxNum = table.bodyMaxNum;\n        for (int i = num; i < bodyMaxNum; i++) {\n            // line fill empty\n            var bodyBuilder = tableRender.bodyContentList.get(i);\n\n            int appendNum = this.keyMaxLen + this.valueMaxLen\n                    + this.prefix.length() + this.suffix.length()\n                    + 2;\n\n            append(bodyBuilder, fill, appendNum);\n        }\n    }\n\n    String renderHead() {\n\n        Ansi.Color color = this.table.color;\n        String head = Ansi.ansi().fg(color).a(this.head).reset().toString();\n\n        var headBuilder = new StringBuilder();\n        headBuilder.append(this.prefix);\n        headBuilder.append(head);\n\n        int num = valueMaxLen + keyMaxLen\n                - prefix.length() - suffix.length() - fill.length()\n                - 1;\n\n        append(headBuilder, this.fill, num);\n\n        headBuilder.append(this.suffix);\n\n        return headBuilder.toString();\n    }\n\n    private void append(StringBuilder builder, String c, int num) {\n        builder.append(c.repeat(Math.max(0, num + 1)));\n    }\n\n    private void keyLine() {\n        this.separatorLine = \"-\".repeat(Math.max(0, this.keyMaxLen)) + \"+\";\n    }\n\n    private void counter(ToyLine line) {\n        if (line.key.length() > this.keyMaxLen) {\n            this.keyMaxLen = line.key.length();\n            this.keyLine();\n        }\n\n        if (line.value.length() > this.valueMaxLen) {\n            this.valueMaxLen = line.value.length();\n        }\n\n        if (this.head.length() > this.keyMaxLen) {\n            this.keyMaxLen = this.head.length();\n            this.keyLine();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/java/com/iohao/game/action/skeleton/toy/ToyTableRender.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.toy;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\nfinal class ToyTableRender {\n    StringBuilder headUpLine = new StringBuilder();\n    StringBuilder headDownLine = new StringBuilder();\n    StringBuilder lastLine = new StringBuilder();\n    StringBuilder headContent = new StringBuilder();\n    List<StringBuilder> bodyContentList = new ArrayList<>();\n\n    ToyTableRender(int bodyMaxNum) {\n        for (int i = 0; i < bodyMaxNum; i++) {\n            this.bodyContentList.add(new StringBuilder(67));\n        }\n    }\n\n    void next() {\n        String nextSeparator = \"##\";\n        this.headDownLine.append(nextSeparator);\n        this.lastLine.append(nextSeparator);\n\n        for (StringBuilder builder : this.bodyContentList) {\n            builder.append(nextSeparator);\n        }\n\n        this.headContent.append(nextSeparator);\n        this.headUpLine.append(nextSeparator);\n    }\n\n    void render() {\n        StringBuilder builder = new StringBuilder(512);\n\n        // table head\n        builder.append(\"+\").append(this.headUpLine).append(\"\\n\");\n        builder.append(\"|\").append(this.headContent).append(\"\\n\");\n        builder.append(\"+\").append(this.headDownLine).append(\"\\n\");\n\n        // table body\n        for (StringBuilder line : this.bodyContentList) {\n            builder.append(\"|\").append(line).append(\"\\n\");\n        }\n\n        // table last\n        builder.append(\"+\").append(this.lastLine);\n        IoGameBanner.println1(builder);\n    }\n\n    void line(int keyMaxLen, int valueMaxLen) {\n        tri(keyMaxLen, valueMaxLen, this.headDownLine);\n        tri(keyMaxLen, valueMaxLen, this.lastLine);\n        tri(keyMaxLen, valueMaxLen, this.headUpLine);\n    }\n\n    void tri(int keyMaxLen, int valueMaxLen, StringBuilder builder) {\n        String c = \"-\";\n\n        append(builder, c, keyMaxLen + 1);\n\n        builder.append(\"+\");\n\n        append(builder, c, valueMaxLen + 1);\n    }\n\n    private void append(StringBuilder builder, String c, int num) {\n        builder.append(String.valueOf(c).repeat(Math.max(0, num + 1)));\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/main/resources/iohao.properties",
    "content": "# common\ngameExternalServer=GameExternalServer\ncmdName=Routing\n# see BrokerClientNodeInfo.java\ngameServerAmount=Number of servers\n# ExternalJoinEnum.java\nconnectionWay=Connection way\n# about brokerServer\ngameBrokerServer=GameBrokerServer\ngameBrokerServerStartupMode=Startup Mode\ngameBrokerServerStartupModeStandalone=Standalone\ngameBrokerServerStartupModeCluster=Cluster\ngameBrokerServerConnectionAmount=Number of GameBrokerServer connections\nbrokerClientRegistrationMessage=Module registration information\n# see PrintActionKit.java\nbusinessFramework=ioGame Business Framework\nbusinessFrameworkPlugin=Plugin\nprintActionKitPrintClose=To turn off printing, see\nprintActionKitPrintFull=Print the full package name, see\nprintActionKitDataCodec=DataCodec\nprintActionKitCheckReturnType=Action return values and parameters do not support set, map and basic types!\n# see DebugInOut.java\ndebugInOutThreadName=Thread\ndebugInOutParamName=RequestParam\ndebugInOutReturnData=ResponseData\ndebugInOutErrorCode=ErrorCode\ndebugInOutErrorMsg=ErrorMsg\ndebugInOutTime=ExecutionTime\n# see StatActionInOut.java\nstatActionInOutTimeRange=%s ms, execute[%d]\nstatActionInOutStatAction=%s, execute[%s], error[%s], avgTime[%d], maxExecuteTime[%s], totalExecuteTime[%s] %s\n# see ThreadMonitorInOut.java\nthreadMonitorInOutThreadMonitor=thread[%s], execute[%d], avgTime[%d]ms, remainTask[%d]\n# see TimeRangeInOut.java\ntimeRangeInOutDayTitle=action execution times[%d]\ntimeRangeInOutHourTitle=%d:00 execute[%s];\ntimeRangeInOutMinuteTitle=[%d~%d minutes, execute:%d]\n# see ActionCommandRegions.java\ncmdMergeLimit=%s exceeds the maximum default value. Please set the capacity manually if necessary. Default maximum capacity %d, current capacity %d\n# see ProtobufCheckActionParserListener.java\nprotobufAnnotationCheck=Note that the protocol class does not have the ProtobufClass annotation added\n# see TextDocumentGenerate.java\ntextDocumentTitle=Game Document Format Description\ntextDocumentBroadcastTitle=Other broadcast interfaces\ntextDocumentCmd=Routing\ntextDocumentBroadcast=Broadcast\ntextDocumentErrorCodeTitle=Error Code\n# see DefaultUserHook.java\nuserHookInto=[PlayerOnline] CountOnline\nuserHookQuit=[PlayerOffline] CountOnline\n# see InternalAboutFlowContext.java\nbindingUserId=Please binding UserId before using this method. see FlowContext.bindingUserId.\n"
  },
  {
    "path": "common/common-core/src/main/resources/iohao_zh_CN.properties",
    "content": "# common\ngameExternalServer=游戏对外服\ncmdName=路由\n# see BrokerClientNodeInfo.java\ngameServerAmount=服务器的数量\n# ExternalJoinEnum.java\nconnectionWay=连接方式\n# about brokerServer\ngameBrokerServer=游戏网关(Broker)\ngameBrokerServerStartupMode=启动模式\ngameBrokerServerStartupModeStandalone=单机模式\ngameBrokerServerStartupModeCluster=集群模式\ngameBrokerServerConnectionAmount=游戏网关的连接数量\nbrokerClientRegistrationMessage=模块注册信息\n# see PrintActionKit.java\nbusinessFramework=ioGame 业务框架\nbusinessFrameworkPlugin=Plugin(插件)\nprintActionKitPrintClose=关闭打印，请看\nprintActionKitPrintFull=打印完整包名，请看\nprintActionKitDataCodec=DataCodec(业务数据编解码)\nprintActionKitCheckReturnType=action 返回值和参数不支持 set、map 和基础类型!\n# see DebugInOut.java\ndebugInOutThreadName=线程\ndebugInOutParamName=参数\ndebugInOutReturnData=响应\ndebugInOutErrorCode=错误码\ndebugInOutErrorMsg=错误信息\ndebugInOutTime=耗时\n# see StatActionInOut.java\nstatActionInOutTimeRange=%s ms 的请求共 [%d] 个\nstatActionInOutStatAction=%s, 执行[%s]次, 异常[%s]次, 平均耗时[%d], 最大耗时[%s], 总耗时[%s] %s\n# see ThreadMonitorInOut.java\nthreadMonitorInOutThreadMonitor=业务线程[%s]，共执行了[%d]次业务，平均耗时[%d]ms, 剩余[%d]个任务未执行\n# see TimeRangeInOut.java\ntimeRangeInOutDayTitle=action 执行次数 共 [%d] 次\ntimeRangeInOutHourTitle=%d:00 共 %s 次;\ntimeRangeInOutMinuteTitle=[%d~%d分钟，%d 次]\n# see ActionCommandRegions.java\ncmdMergeLimit=%s 超过最大默认值，如有需要请手动设置容量。默认最大容量 %d. 当前容量 %d\n# see ProtobufCheckActionParserListener.java\nprotobufAnnotationCheck=注意，协议类没有添加 ProtobufClass 注解\n# see TextDocumentGenerate.java\ntextDocumentTitle=游戏文档格式说明\ntextDocumentBroadcastTitle=其它广播推送\ntextDocumentCmd=路由\ntextDocumentBroadcast=广播\ntextDocumentErrorCodeTitle=错误码\n# see DefaultUserHook.java\nuserHookInto=[玩家上线] 在线数量\nuserHookQuit=[玩家下线] 在线数量\n# see InternalAboutFlowContext.java\nbindingUserId=请登录后使用该方法. see FlowContext.bindingUserId.\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/ActionParserListenerTest.java",
    "content": "package com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.action.ExampleActionCmd;\nimport com.iohao.game.action.skeleton.core.action.pojo.BeeApple;\nimport com.iohao.game.action.skeleton.core.action.pojo.DogValid;\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2024-05-01\n */\n@Slf4j\npublic class ActionParserListenerTest {\n    BarSkeleton barSkeleton;\n\n    @Before\n    public void setUp() throws InterruptedException {\n        BarSkeletonBuilder builder = TestDataKit.createBuilder();\n\n        barSkeleton = builder.build();\n\n        // 等待 protobuf proxy class 加载完成。 see JProtobufParserActionListener\n        TimeUnit.MILLISECONDS.sleep(1000);\n    }\n\n    @Test\n    public void onActionCommand() {\n        long l = System.currentTimeMillis();\n\n        extractedBeeHello();\n        extractedBeeHelloDog();\n\n        log.info(\"l : {}\", System.currentTimeMillis() - l);\n    }\n\n    private void extractedBeeHello() {\n        var bizData = new BeeApple();\n        bizData.content = \"a\";\n\n        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.hello);\n        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, bizData);\n        flowContext.inOutStartTime();\n\n        barSkeleton.handle(flowContext);\n\n        var data = flowContext.getResponse().getData(BeeApple.class);\n\n        Assert.assertEquals(data.content, \"a，I'm hello\");\n    }\n\n    private void extractedBeeHelloDog() {\n        var bizData = new DogValid();\n        bizData.name = \"a\";\n\n        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.hello_dog);\n        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, bizData);\n        flowContext.inOutStartTime();\n\n        barSkeleton.handle(flowContext);\n\n        var data = flowContext.getResponse().getData(DogValid.class);\n\n        Assert.assertEquals(data.name, \"a\");\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/BarSkeletonTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.action.ExampleActionCmd;\nimport com.iohao.game.action.skeleton.core.action.pojo.BeeApple;\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Before;\nimport org.junit.Test;\n\n@Slf4j\npublic class BarSkeletonTest {\n    BarSkeleton barSkeleton;\n\n    @Before\n    public void setUp() {\n        // 构建业务框架\n        barSkeleton = TestDataKit.newBarSkeleton();\n    }\n\n    @Test\n    public void newBuilder() {\n\n        // 模拟路由信息\n        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.hello);\n\n        // 模拟请求数据 （一般由前端传入）\n        BeeApple beeApple = new BeeApple();\n        beeApple.setContent(\"hello 塔姆!\");\n        beeApple.setId(101);\n\n        var flowContext = TestDataKit.ofFlowContext(cmdInfo, beeApple);\n\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n        IoGameBanner.printLine();\n\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n        IoGameBanner.printLine();\n    }\n\n    @Test\n    public void testVoid() {\n\n        // 模拟路由信息\n        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.test_void);\n\n        // 模拟请求数据 （一般由前端传入）\n        BeeApple beeApple = new BeeApple();\n        beeApple.setContent(\"hello 塔姆!\");\n        beeApple.setId(1010);\n\n        var flowContext = TestDataKit.ofFlowContext(cmdInfo, beeApple);\n\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n        IoGameBanner.printLine();\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/DataCodecKitTest.java",
    "content": "package com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.protocol.Student;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * @author 渔民小镇\n * @date 2024-06-11\n */\npublic class DataCodecKitTest {\n\n    @Test\n    public void decode() {\n        Student student = new Student();\n        student.name = \"a\";\n\n        byte[] encode = DataCodecKit.encode(student);\n        Student decode = DataCodecKit.decode(encode, Student.class);\n        Assert.assertEquals(student.name, decode.name);\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/InOutManagerTest.java",
    "content": "package com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.flow.ActionMethodInOut;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowOption;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-02\n */\n@Slf4j\npublic class InOutManagerTest {\n    FlowContext flowContext = new FlowContext();\n    static FlowOption<List<String>> resultListOption = FlowOption.valueOf(\"resultListOption\");\n\n    @Test\n    public void test() {\n        List<ActionMethodInOut> inOutList = new ArrayList<>();\n        inOutList.add(new A_ActionMethodInOut());\n        inOutList.add(new B_ActionMethodInOut());\n        inOutList.add(new C_ActionMethodInOut());\n\n        IoGameBanner.printlnMsg(\"------ 测试多个 inout ------\");\n        extracted(InOutManager.ofAbcAbc(), \"Ain Bin Cin Aout Bout Cout\", inOutList);\n        extracted(InOutManager.ofPipeline(), \"Ain Bin Cin Cout Bout Aout\", inOutList);\n\n        // 测试单个 inout\n        IoGameBanner.printlnMsg(\"------ 测试 1 个 inout ------\");\n        extracted(InOutManager.ofAbcAbc(), \"Ain Aout\", List.of(new A_ActionMethodInOut()));\n        extracted(InOutManager.ofPipeline(), \"Ain Aout\", List.of(new A_ActionMethodInOut()));\n\n        // 测试 0 个 inout\n        IoGameBanner.printlnMsg(\"------ 测试 0 个 inout ------\");\n        extracted(InOutManager.ofAbcAbc(), \"\", Collections.emptyList());\n        extracted(InOutManager.ofPipeline(), \"\", Collections.emptyList());\n    }\n\n    private void extracted(InOutManager inOutManager, String result, List<ActionMethodInOut> inOutList) {\n\n        for (ActionMethodInOut actionMethodInOut : inOutList) {\n            inOutManager.addInOut(actionMethodInOut);\n        }\n\n        flowContext.option(resultListOption, new ArrayList<>());\n        inOutManager.fuckIn(flowContext);\n        inOutManager.fuckOut(flowContext);\n\n        List<String> resultList = flowContext.option(resultListOption);\n        var line = String.join(\" \", resultList);\n        Assert.assertEquals(line, result);\n\n        IoGameBanner.printLine();\n    }\n}\n\n@Slf4j\nclass A_ActionMethodInOut implements ActionMethodInOut {\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n        log.info(\"A in\");\n        flowContext.option(InOutManagerTest.resultListOption).add(\"Ain\");\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        log.info(\"A out\");\n        flowContext.option(InOutManagerTest.resultListOption).add(\"Aout\");\n    }\n}\n\n@Slf4j\nclass B_ActionMethodInOut implements ActionMethodInOut {\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n        log.info(\"B in\");\n        flowContext.option(InOutManagerTest.resultListOption).add(\"Bin\");\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        log.info(\"B out\");\n        flowContext.option(InOutManagerTest.resultListOption).add(\"Bout\");\n    }\n}\n\n@Slf4j\nclass C_ActionMethodInOut implements ActionMethodInOut {\n    @Override\n    public void fuckIn(FlowContext flowContext) {\n        log.info(\"C in\");\n        flowContext.option(InOutManagerTest.resultListOption).add(\"Cin\");\n    }\n\n    @Override\n    public void fuckOut(FlowContext flowContext) {\n        log.info(\"C out\");\n        flowContext.option(InOutManagerTest.resultListOption).add(\"Cout\");\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380Test.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.action.ExampleActionCmd;\nimport com.iohao.game.action.skeleton.core.action.pojo.DogValid;\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2022-07-09\n */\n@Slf4j\npublic class JSR380Test {\n\n    BarSkeleton barSkeleton;\n\n    @Before\n    public void setUp() {\n        BarSkeletonBuilder builder = TestDataKit.createBuilder();\n        builder.getSetting().setValidator(true);\n\n        barSkeleton = builder.build();\n    }\n\n    @Test\n    public void jsr380() {\n        DogValid dogValid = new DogValid();\n\n        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.jsr380);\n\n        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, dogValid);\n\n        barSkeleton.handle(flowContext);\n\n        Assert.assertTrue(flowContext.getResponse().hasError());\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380ValidatedGroupTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.action.ExampleActionCmd;\nimport com.iohao.game.action.skeleton.core.action.pojo.BirdValid;\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/**\n * @author fangwei\n * @date 2022-09-20\n */\npublic class JSR380ValidatedGroupTest {\n    BarSkeleton barSkeleton;\n\n    @Before\n    public void setUp() {\n        BarSkeletonBuilder builder = TestDataKit.createBuilder();\n        builder.getSetting().setValidator(true);\n\n        barSkeleton = builder.build();\n    }\n\n    @Test\n    public void updateGroupTest() {\n        BirdValid birdValid = new BirdValid();\n        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.validated_group_update);\n\n        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, birdValid);\n\n        barSkeleton.handle(flowContext);\n\n        Assert.assertTrue(flowContext.getResponse().hasError());\n    }\n\n    @Test\n    public void createGroupTest() {\n        BirdValid birdValid = new BirdValid();\n        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.validated_group_create);\n\n        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, birdValid);\n\n        barSkeleton.handle(flowContext);\n\n        Assert.assertTrue(flowContext.getResponse().hasError());\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/SimpleWrapperActionTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.action.SimpleWrapperAction;\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValue;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static com.iohao.game.action.skeleton.core.action.ExampleActionCmd.SimpleWrapperActionActionCmd.*;\n\n/**\n * @author 渔民小镇\n * @date 2024-05-02\n */\n@Slf4j\npublic class SimpleWrapperActionTest {\n\n    BarSkeleton barSkeleton;\n\n    @Before\n    public void setUp() {\n        barSkeleton = TestDataKit.createBuilder(SimpleWrapperAction.class::equals).build();\n    }\n\n    @Test\n    public void testInt() {\n        CmdInfo cmdInfo = CmdInfo.of(cmd, testInt);\n\n        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, IntValue.of(100));\n\n        barSkeleton.handle(flowContext);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperIntTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValueList;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValue;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.ArrayList;\n\nimport static com.iohao.game.action.skeleton.core.action.ExampleActionCmd.WrapperIntActionCmd;\n\n/**\n * @author 渔民小镇\n * @date 2022-06-26\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class WrapperIntTest {\n\n    private CmdInfo of(int subCmd) {\n        return CmdInfo.of(WrapperIntActionCmd.cmd, subCmd);\n    }\n\n    private FlowContext createIntValueFlowContext(int subCmd) {\n        CmdInfo cmdInfo = this.of(subCmd);\n\n        IntValue intValue = new IntValue();\n        intValue.value = 100;\n\n        return TestDataKit.ofFlowContext(cmdInfo, intValue);\n    }\n\n    BarSkeleton barSkeleton;\n\n    @Before\n    public void setUp() {\n        barSkeleton = TestDataKit.newBarSkeleton();\n    }\n\n\n    @Test\n    public void intValue1() {\n        FlowContext flowContext;\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.intValue2Void);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.intValue2Int);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.intValue2IntValue);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.intValue2IntList);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.intListVoid);\n        IntValueList intValueList = new IntValueList();\n        intValueList.values = new ArrayList<>();\n        intValueList.values.add(1);\n        intValueList.values.add(3);\n        intValueList.values.add(5);\n\n        RequestMessage request = flowContext.getRequest();\n        request.setData(intValueList);\n\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n    }\n\n    //    @Test\n    public void intValue2() {\n        FlowContext flowContext;\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.int2Void);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.int2Int);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.int2IntValue);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.int2IntList);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n    }\n\n    @Test\n    public void integerValue() {\n        FlowContext flowContext;\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.integer2Void);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.integer2Integer);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.integer2IntValue);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.integer2IntegerList);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperLongTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core;\n\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.wrapper.LongValue;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static com.iohao.game.action.skeleton.core.action.ExampleActionCmd.WrapperLongActionCmd;\n\n/**\n * @author 渔民小镇\n * @date 2022-06-26\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class WrapperLongTest {\n\n    private CmdInfo of(int subCmd) {\n        return CmdInfo.of(WrapperLongActionCmd.cmd, subCmd);\n    }\n\n    private FlowContext createLongValueFlowContext(int subCmd) {\n        CmdInfo cmdInfo = this.of(subCmd);\n\n        LongValue longValue = new LongValue();\n        longValue.value = 100;\n\n        return TestDataKit.ofFlowContext(cmdInfo, longValue);\n    }\n\n    BarSkeleton barSkeleton;\n\n    @Before\n    public void setUp() {\n        barSkeleton = TestDataKit.newBarSkeleton();\n    }\n\n    @Test\n    public void longValue1() {\n        FlowContext flowContext = null;\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longValue2Void);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longValue2Long);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longValue2LongValue);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longValue2LongList);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n    }\n\n    @Test\n    public void longValue2() {\n        FlowContext flowContext = null;\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.long2Void);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.long2Long);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.long2LongValue);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.long2LongList);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n    }\n\n    @Test\n    public void longerValue3() {\n        FlowContext flowContext;\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longer2Void);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longer2Long);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longer2LongValue);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n\n        flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longer2LongList);\n        // 业务框架处理用户请求\n        barSkeleton.handle(flowContext);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/BeeAction.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\nimport com.iohao.game.action.skeleton.annotation.ValidatedGroup;\nimport com.iohao.game.action.skeleton.core.action.group.Create;\nimport com.iohao.game.action.skeleton.core.action.group.Update;\nimport com.iohao.game.action.skeleton.core.action.pojo.BeeApple;\nimport com.iohao.game.action.skeleton.core.action.pojo.BirdValid;\nimport com.iohao.game.action.skeleton.core.action.pojo.DogValid;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.extern.slf4j.Slf4j;\n\n@Slf4j(topic = IoGameLogName.CommonStdout)\n@ActionController(ExampleActionCmd.BeeActionCmd.cmd)\npublic class BeeAction {\n    /**\n     * <pre>\n     *     打招呼\n     *     实现注解 ActionMethod 告知框架这是一个对外开放的action (即一个方法就是一个对外的处理)\n     * </pre>\n     *\n     * @param beeApple a\n     * @return 返回具体信息\n     */\n    @ActionMethod(ExampleActionCmd.BeeActionCmd.hello)\n    public BeeApple hello(BeeApple beeApple) {\n        BeeApple that = new BeeApple();\n        that.setContent(beeApple.content + \"，I'm hello\");\n        return that;\n    }\n\n    @ActionMethod(ExampleActionCmd.BeeActionCmd.name)\n    public BeeApple name(BeeApple beeApple) {\n        log.debug(\"beeApple: {}\", beeApple);\n        BeeApple that = new BeeApple();\n        that.setContent(beeApple.content + \"，I'm name\");\n        return that;\n    }\n\n    @ActionMethod(ExampleActionCmd.BeeActionCmd.test_void)\n    public void thatVoid(BeeApple beeApple) {\n        BeeApple that = new BeeApple();\n        that.setContent(beeApple.content + \"，I'm thatVoid\");\n    }\n\n    @ActionMethod(ExampleActionCmd.BeeActionCmd.hello_dog)\n    public DogValid helloDog(DogValid dogValid) {\n        log.info(\"dogValid : {}\", dogValid);\n        return dogValid;\n    }\n\n    @ActionMethod(ExampleActionCmd.BeeActionCmd.jsr380)\n    public void jsr380(DogValid dogValid) {\n        log.info(\"{}\", dogValid);\n    }\n\n    @ActionMethod(ExampleActionCmd.BeeActionCmd.validated_group_update)\n    public void validateUpdate(@ValidatedGroup(value = Update.class) BirdValid birdValid) {\n        log.info(\"{}\", birdValid);\n    }\n\n    @ActionMethod(ExampleActionCmd.BeeActionCmd.validated_group_create)\n    public void validateCreate(@ValidatedGroup(value = Create.class) BirdValid birdValid) {\n        log.info(\"{}\", birdValid);\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/ExampleActionCmd.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action;\n\n\npublic interface ExampleActionCmd {\n    /**\n     * bee 模块功能\n     */\n    interface BeeActionCmd {\n        /**\n         * bee 模块 - 主 cmd\n         */\n        int cmd = 10;\n\n        int hello = 0;\n        int name = 1;\n        int test_void = 3;\n        int jsr380 = 4;\n        int validated_group_update = 5;\n        int validated_group_create = 6;\n        int hello_dog = 7;\n    }\n\n\n    interface WrapperIntActionCmd {\n        /**\n         * bee 模块 - 主 cmd\n         */\n        int cmd = 11;\n\n        int intValue2Void = 0;\n        int intValue2Int = 1;\n        int intValue2IntValue = 2;\n        int intValue2IntList = 3;\n        int intListVoid = 12;\n\n\n        int int2Void = 4;\n        int int2Int = 5;\n        int int2IntValue = 6;\n        int int2IntList = 7;\n\n        int integer2Void = 8;\n        int integer2Integer = 9;\n        int integer2IntValue = 10;\n        int integer2IntegerList = 11;\n\n    }\n\n    interface WrapperLongActionCmd {\n        /**\n         * bee 模块 - 主 cmd\n         */\n        int cmd = 12;\n\n        int longValue2Void = 0;\n        int longValue2Long = 1;\n        int longValue2LongValue = 2;\n        int longValue2LongList = 3;\n\n        int long2Void = 4;\n        int long2Long = 5;\n        int long2LongValue = 6;\n        int long2LongList = 7;\n\n        int longer2Void = 8;\n        int longer2Long = 9;\n        int longer2LongValue = 10;\n        int longer2LongList = 11;\n    }\n\n    interface SimpleWrapperActionActionCmd {\n        int cmd = 13;\n        int testInt = 0;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/SimpleWrapperAction.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2024-05-02\n */\n@Slf4j\n@ActionController(ExampleActionCmd.SimpleWrapperActionActionCmd.cmd)\npublic class SimpleWrapperAction {\n    @ActionMethod(ExampleActionCmd.SimpleWrapperActionActionCmd.testInt)\n    public void testInt(int age) {\n        IoGameBanner.println1(age);\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/WrapperIntAction.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValue;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static com.iohao.game.action.skeleton.core.action.ExampleActionCmd.WrapperIntActionCmd;\n\n/**\n * @author 渔民小镇\n * @date 2022-06-26\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\n@ActionController(WrapperIntActionCmd.cmd)\npublic class WrapperIntAction {\n    @ActionMethod(WrapperIntActionCmd.intValue2Void)\n    public void intValue2Void(IntValue intValue) {\n        log.info(\"intValue : {}\", intValue);\n    }\n\n    @ActionMethod(WrapperIntActionCmd.intValue2Int)\n    public int intValue2Int(IntValue intValue) {\n        return intValue.value + 1;\n    }\n\n    @ActionMethod(WrapperIntActionCmd.intValue2IntValue)\n    public IntValue intValue2IntValue(IntValue intValue) {\n\n        IntValue newIntValue = new IntValue();\n        newIntValue.value = intValue.value + 2;\n\n        return newIntValue;\n    }\n\n    @ActionMethod(WrapperIntActionCmd.intValue2IntList)\n    public List<Integer> intValue2IntList(IntValue intValue) {\n\n        List<Integer> intList = new ArrayList<>();\n        intList.add(intValue.value);\n        intList.add(intValue.value + 3);\n\n        return intList;\n    }\n\n    @ActionMethod(WrapperIntActionCmd.intListVoid)\n    public List<Integer> IntListVoid(List<Integer> intList) {\n\n        log.info(\"intList : {}\", intList);\n        return Collections.emptyList();\n    }\n\n    @ActionMethod(WrapperIntActionCmd.int2Void)\n    public void int2Void(int intValue) {\n    }\n\n    @ActionMethod(WrapperIntActionCmd.int2Int)\n    public int int2Int(int intValue) {\n        return intValue + 1;\n    }\n\n    @ActionMethod(WrapperIntActionCmd.int2IntValue)\n    public IntValue int2IntValue(int intValue) {\n\n        IntValue newIntValue = new IntValue();\n        newIntValue.value = intValue + 2;\n\n        return newIntValue;\n    }\n\n    @ActionMethod(WrapperIntActionCmd.int2IntList)\n    public List<Integer> int2IntList(int intValue) {\n        log.info(\"intValue : {}\", intValue);\n\n        List<Integer> intList = new ArrayList<>();\n        intList.add(intValue);\n        intList.add(intValue + 3);\n\n        return intList;\n    }\n\n\n    @ActionMethod(WrapperIntActionCmd.integer2Void)\n    public void integer2Void(Integer intValue) {\n    }\n\n    @ActionMethod(WrapperIntActionCmd.integer2Integer)\n    public Integer integer2Integer(Integer intValue) {\n        return intValue + 1;\n    }\n\n    @ActionMethod(WrapperIntActionCmd.integer2IntValue)\n    public IntValue integer2IntValue(Integer intValue) {\n\n        IntValue newIntValue = new IntValue();\n        newIntValue.value = intValue + 2;\n\n        return newIntValue;\n    }\n\n    @ActionMethod(WrapperIntActionCmd.integer2IntegerList)\n    public List<Integer> integer2IntegerList(Integer intValue) {\n        log.info(\"intValue : {}\", intValue);\n\n        List<Integer> intList = new ArrayList<>();\n        intList.add(intValue);\n        intList.add(intValue + 3);\n\n        return intList;\n    }\n\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/WrapperLongAction.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.annotation.ActionMethod;\nimport com.iohao.game.action.skeleton.protocol.wrapper.LongValue;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static com.iohao.game.action.skeleton.core.action.ExampleActionCmd.WrapperLongActionCmd;\n\n/**\n * @author 渔民小镇\n * @date 2022-06-26\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\n@ActionController(WrapperLongActionCmd.cmd)\npublic class WrapperLongAction {\n    @ActionMethod(WrapperLongActionCmd.longValue2Void)\n    public void longValue(LongValue longValue) {\n        log.info(\"longValue : {}\", longValue);\n    }\n\n    @ActionMethod(WrapperLongActionCmd.longValue2Long)\n    public long longValue2Long(LongValue longValue) {\n        return longValue.value + 1;\n    }\n\n    @ActionMethod(WrapperLongActionCmd.longValue2LongValue)\n    public LongValue longValue2LongValue(LongValue longValue) {\n\n        LongValue newLongValue = new LongValue();\n        newLongValue.value = longValue.value + 2;\n\n        return newLongValue;\n    }\n\n    @ActionMethod(WrapperLongActionCmd.longValue2LongList)\n    public List<Long> longValue2LongList(LongValue longValue) {\n\n        List<Long> list = new ArrayList<>();\n        list.add(longValue.value);\n        list.add(longValue.value + 3);\n\n        return list;\n    }\n\n\n    @ActionMethod(WrapperLongActionCmd.long2Void)\n    public void long2Void(long value) {\n        log.info(\"value : {}\", value);\n    }\n\n    @ActionMethod(WrapperLongActionCmd.long2Long)\n    public long long2Long(long value) {\n        return value + 1;\n    }\n\n    @ActionMethod(WrapperLongActionCmd.long2LongValue)\n    public LongValue long2LongValue(long value) {\n\n        LongValue newLongValue = new LongValue();\n        newLongValue.value = value + 2;\n\n        return newLongValue;\n    }\n\n    @ActionMethod(WrapperLongActionCmd.long2LongList)\n    public List<Long> long2LongList(long value) {\n\n        List<Long> list = new ArrayList<>();\n        list.add(value);\n        list.add(value + 3);\n\n        return list;\n    }\n\n    @ActionMethod(WrapperLongActionCmd.longer2Void)\n    public void longer2Void(LongValue longValue) {\n        log.info(\"longValue : {}\", longValue);\n    }\n\n    @ActionMethod(WrapperLongActionCmd.longer2Long)\n    public long longer2Long(long value) {\n        return value + 1;\n    }\n\n    @ActionMethod(WrapperLongActionCmd.longer2LongValue)\n    public LongValue longer2LongValue(Long value) {\n\n        LongValue newLongValue = new LongValue();\n        newLongValue.value = value + 2;\n\n        return newLongValue;\n    }\n\n    @ActionMethod(WrapperLongActionCmd.longer2LongList)\n    public List<Long> longer2LongList(Long value) {\n\n        List<Long> intList = new ArrayList<>();\n        intList.add(value);\n        intList.add(value + 3);\n\n        return intList;\n    }\n\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/group/Create.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.group;\n\n/**\n * 创建组\n *\n * @author fangwei\n * @date 2022-09-20\n */\npublic interface Create {\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/group/Update.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.group;\n\n/**\n * 更新组\n *\n * @author fangwei\n * @date 2022-09-20\n */\npublic interface Update {\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/pojo/BeeApple.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.pojo;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.AccessLevel;\nimport lombok.Data;\nimport lombok.experimental.FieldDefaults;\n\n@Data\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class BeeApple {\n    int id;\n    String content;\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/pojo/BirdValid.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.pojo;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.action.skeleton.core.action.group.Create;\nimport com.iohao.game.action.skeleton.core.action.group.Update;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n *\n * @author fangwei\n * @date 2022-09-20\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class BirdValid {\n\n    @NotNull(groups = Update.class)\n    String nickname;\n\n    @NotNull(groups = Create.class)\n    String headPortrait;\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/pojo/DogValid.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.pojo;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport jakarta.validation.constraints.Min;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-16\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class DogValid {\n    @NotNull(message = \"名字不能为 null\")\n    String name;\n\n    @NotNull(message = \"不能为 null\")\n    @Size(min = 2, max = 14)\n    String licensePlate;\n\n    @Min(2)\n    int seatCount;\n\n    int age;\n\n    String nickname;\n\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/pojo/Snake.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.action.pojo;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-08\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class Snake {\n    int id;\n    String name;\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/data/TestDataKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.data;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.core.*;\nimport com.iohao.game.action.skeleton.core.action.BeeAction;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.core.flow.internal.DebugInOut;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.ClassScanner;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Predicate;\n\n@UtilityClass\npublic class TestDataKit {\n\n    public BarSkeleton newBarSkeleton() {\n        return createBuilder().build();\n    }\n\n    public BarSkeletonBuilder createBuilder(Predicate<Class<?>> appendPredicateFilter) {\n        // 尽量做到所有操作是可插拔的. 详细配置 see BarSkeletonBuilder.build\n        BarSkeletonBuilder builder = BarSkeleton.newBuilder();\n\n        builder.addInOut(new DebugInOut());\n\n        builder.setActionAfter(flowContext -> IoGameBanner.printLine());\n\n        List<Class<?>> classList = getClasses(appendPredicateFilter);\n\n        classList.forEach(builder::addActionController);\n\n        BarSkeletonSetting setting = builder.getSetting();\n        setting.setPrintHandler(false);\n        setting.setPrintInout(false);\n        setting.setPrintDataCodec(false);\n        setting.setPrintRunners(false);\n        setting.setPrintHandler(false);\n\n        return builder;\n    }\n\n    private List<Class<?>> getClasses(Predicate<Class<?>> appendPredicateFilter) {\n        Predicate<Class<?>> predicateFilter = (clazz) -> {\n            if (clazz.getAnnotation(ActionController.class) == null) {\n                return false;\n            }\n\n            if (Objects.nonNull(appendPredicateFilter)) {\n                if (appendPredicateFilter.test(clazz)) {\n                    return true;\n                }\n\n                return false;\n            }\n\n            return true;\n        };\n\n        String packagePath = BeeAction.class.getPackageName();\n        ClassScanner classScanner = new ClassScanner(packagePath, predicateFilter);\n        return classScanner.listScan();\n    }\n\n    public BarSkeletonBuilder createBuilder() {\n        return createBuilder(null);\n    }\n\n    public FlowContext ofFlowContext(CmdInfo cmdInfo) {\n        return ofFlowContext(cmdInfo, null);\n    }\n\n    public FlowContext ofFlowContext(CmdInfo cmdInfo, Object data) {\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(cmdInfo, data);\n\n        FlowContext flowContext = new FlowContext();\n        flowContext.setRequest(requestMessage);\n\n        return flowContext;\n    }\n\n    public FlowContext ofFlowContext() {\n        FlowContext flowContext = new FlowContext();\n\n        var builder = new BarSkeletonBuilderParamConfig().createBuilder();\n        var setting = builder.getSetting();\n        setting.setPrint(false);\n        var skeleton = builder.build();\n        flowContext.setBarSkeleton(skeleton);\n\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(CmdInfo.of(1, 1));\n        flowContext.setRequest(requestMessage);\n\n        var threadExecutor = flowContext.getThreadExecutor();\n        flowContext.option(FlowAttr.threadExecutor, threadExecutor);\n\n        return flowContext;\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/flow/internal/StatActionInOutTest.java",
    "content": "package com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.RandomKit;\nimport org.junit.Test;\n\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2023-11-19\n */\npublic class StatActionInOutTest {\n\n    @Test\n    public void testStatActionInOut() {\n\n        StatActionInOut inOut = new StatActionInOut();\n        final StatActionInOut.StatActionRegion region = inOut.region;\n        setListener(inOut);\n\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(CmdInfo.of(1, 1));\n        FlowContext flowContext = new FlowContext();\n        flowContext.setRequest(requestMessage);\n        extracted(region, flowContext);\n\n        requestMessage = BarMessageKit.createRequestMessage(CmdInfo.of(1, 2));\n        flowContext.setRequest(requestMessage);\n        extracted(region, flowContext);\n\n        region.stream().forEach(IoGameBanner::println1);\n    }\n\n    private void setListener(StatActionInOut inOut) {\n        inOut.setListener(new StatActionInOut.StatActionChangeListener() {\n            @Override\n            public void changed(StatActionInOut.StatAction statAction, long time, FlowContext flowContext) {\n\n            }\n\n            @Override\n            public boolean triggerUpdateTimeRange(StatActionInOut.StatAction statAction, long time, FlowContext flowContext) {\n                return time >= 500;\n//                return false;\n            }\n\n            @Override\n            public List<StatActionInOut.TimeRange> createTimeRangeList() {\n                return List.of(\n                        StatActionInOut.TimeRange.create(1000, 1999),\n                        StatActionInOut.TimeRange.create(2000, Long.MAX_VALUE, \"> 2000\"));\n            }\n        });\n    }\n\n    private static void extracted(StatActionInOut.StatActionRegion region, FlowContext flowContext) {\n        for (int i = 0; i < 50; i++) {\n            // 模拟执行时间\n            int time = RandomKit.random(500, 3000);\n\n            region.update(time, flowContext);\n        }\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/flow/internal/ThreadMonitorInOutTest.java",
    "content": "package com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2024-10-06\n * @since 21.18\n */\n@Slf4j\npublic class ThreadMonitorInOutTest {\n\n    @Test\n    public void fuckIn() {\n        FlowContext flowContext = TestDataKit.ofFlowContext();\n\n        ThreadMonitorInOut threadMonitorInOut = new ThreadMonitorInOut();\n        threadMonitorInOut.fuckIn(flowContext);\n\n        threadMonitorInOut.fuckOut(flowContext);\n        threadMonitorInOut.fuckOut(flowContext);\n\n        var region = threadMonitorInOut.getRegion();\n        log.info(\"region : {}\", region);\n        Assert.assertEquals(region.map.size(), 1);\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/core/flow/internal/TimeRangeInOutTest.java",
    "content": "package com.iohao.game.action.skeleton.core.flow.internal;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.data.TestDataKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.RandomKit;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.time.LocalDate;\nimport java.time.LocalTime;\nimport java.util.List;\nimport java.util.concurrent.atomic.LongAdder;\n\n/**\n * @author 渔民小镇\n * @date 2023-11-29\n */\npublic class TimeRangeInOutTest {\n\n    @Test\n    public void fuckOut() {\n        FlowContext flowContext = TestDataKit.ofFlowContext(CmdInfo.of(1, 1));\n\n        TimeRangeInOut inOut = new TimeRangeInOut();\n        setListener(inOut);\n\n        TimeRangeInOut.TimeRangeDayRegion region = inOut.region;\n\n        LocalDate localDate = LocalDate.now();\n\n        LongAdder count = new LongAdder();\n        for (int i = 0; i < 10000; i++) {\n            int hour = RandomKit.randomInt(24);\n            int minute = RandomKit.randomInt(60);\n            LocalTime localTime = LocalTime.of(hour, minute);\n            region.update(localDate, localTime, flowContext);\n            count.increment();\n        }\n\n        var timeRangeDay = region.getTimeRangeDay(localDate);\n        Assert.assertEquals(timeRangeDay.count().sum(), count.sum());\n\n        print(inOut);\n    }\n\n    private static void print(TimeRangeInOut inOut) {\n        TimeRangeInOut.TimeRangeDayRegion region = inOut.region;\n\n        // 取指定日期的 TimeRangeDay 对象\n        LocalDate localDate = LocalDate.now();\n        TimeRangeInOut.TimeRangeDay timeRangeDay = region.getTimeRangeDay(localDate);\n        IoGameBanner.println1(timeRangeDay);\n    }\n\n    private void setListener(TimeRangeInOut inOut) {\n        inOut.setListener(new TimeRangeInOut.ChangeListener() {\n            @Override\n            public void callbackYesterday(TimeRangeInOut.TimeRangeDay timeRangeYesterday) {\n                // 昨日的全天统计数据对象\n                IoGameBanner.println1(timeRangeYesterday);\n\n                timeRangeYesterday.stream().forEach(timeRangeHour -> {\n                    // 几点\n                    int hour = timeRangeHour.getHour();\n                    // 一小时的 action 调用次数\n                    LongAdder count = timeRangeHour.count();\n\n                    // 该小时的分钟阶段\n                    List<TimeRangeInOut.TimeRangeMinute> timeRangeMinutes = timeRangeHour.minuteList();\n                    for (TimeRangeInOut.TimeRangeMinute timeRangeMinute : timeRangeMinutes) {\n                        // 该分钟阶段的 action 调用次数\n                        LongAdder minuteCount = timeRangeMinute.count();\n                    }\n\n                });\n            }\n\n            @Override\n            public List<TimeRangeInOut.TimeRangeMinute> createListenerTimeRangeMinuteList() {\n                return List.of(\n                        TimeRangeInOut.TimeRangeMinute.create(0, 29),\n                        TimeRangeInOut.TimeRangeMinute.create(30, 59)\n                );\n            }\n        });\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/eventbus/CustomEvent.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.concurrent.atomic.LongAdder;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@Slf4j\n@EventBusSubscriber\npublic class CustomEvent {\n    static LongAdder myMessageLong = new LongAdder();\n    static LongAdder myRecordLong = new LongAdder();\n\n    @EventSubscribe(order = 1, value = ExecutorSelector.simpleExecutor)\n    public void myMessage1(MyMessage message) {\n        log.info(\"###myMessage1 : {}\", message);\n        myMessageLong.increment();\n    }\n\n    @EventSubscribe(order = 3, value = ExecutorSelector.simpleExecutor)\n    public void myMessage3(MyMessage message) {\n        log.info(\"###myMessage3 : {}\", message);\n        myMessageLong.increment();\n    }\n\n    @EventSubscribe(order = 2, value = ExecutorSelector.simpleExecutor)\n    public void myMessage2(MyMessage message) {\n        log.info(\"###myMessage2 : {}\", message);\n        myMessageLong.increment();\n    }\n\n    @EventSubscribe\n    public void myRecord1(MyRecord message) {\n        log.info(\"myRecord1 : {}\", message);\n        myRecordLong.increment();\n    }\n\n    @EventSubscribe\n    public void myRecord2(MyRecord message) {\n        log.info(\"myRecord2 : {} - {}\", message, message.getClass());\n        myRecordLong.increment();\n    }\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/eventbus/EventBusTest.java",
    "content": "package com.iohao.game.action.skeleton.eventbus;\n\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.AfterClass;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@Slf4j\npublic class EventBusTest {\n    EventBus eventBus = new DefaultEventBus(\"1\");\n    EventBus eventBus2 = new DefaultEventBus(\"2\");\n\n    @Before\n    public void setUp() {\n        initEventBus(eventBus);\n        initEventBus(eventBus2);\n\n//        initEventBus(eventBus2);\n    }\n\n    @AfterClass\n    public static void afterClass() throws Exception {\n        TimeUnit.MILLISECONDS.sleep(50);\n    }\n\n    private void initEventBus(EventBus eventBus) {\n        String id = eventBus.getId();\n        EventBrokerClientMessage eventBrokerClientMessage = this.createEventBrokerClientMessage(id);\n        eventBus.setEventBrokerClientMessage(eventBrokerClientMessage);\n\n        eventBus.setSubscribeExecutorStrategy(SubscribeExecutorStrategy.defaultInstance());\n        eventBus.setSubscriberInvokeCreator(SubscriberInvokeCreator.defaultInstance());\n        eventBus.setEventBusMessageCreator(EventBusMessageCreator.defaultInstance());\n        eventBus.setEventBusListener(EventBusListener.defaultInstance());\n        eventBus.setExecutorRegion(ExecutorRegionKit.getExecutorRegion());\n\n        // 注册订阅者\n        eventBus.register(new CustomEvent());\n\n        Set<String> topic = eventBus.listTopic();\n        eventBrokerClientMessage.setEventTopicMessage(new EventTopicMessage(topic));\n\n        EventBusRegion.addLocal(eventBus);\n\n        try {\n            TimeUnit.MILLISECONDS.sleep(1);\n        } catch (InterruptedException e) {\n            ThrowKit.ofRuntimeException(e);\n        }\n    }\n\n    private EventBrokerClientMessage createEventBrokerClientMessage(String appId) {\n        String appName = \"testAppName-\" + appId;\n        String tag = \"testTag\";\n        String typeName = \"LOGIC\";\n\n        return new EventBrokerClientMessage(appName, tag, typeName, appId);\n    }\n\n    @Test\n    public void fireAny() {\n        MyMessage message = new MyMessage();\n        message.setName(\"fireAny\");\n\n        this.eventBus.fireAnySync(message);\n        Assert.assertEquals(3, CustomEvent.myMessageLong.sum());\n\n        IoGameBanner.printLine();\n        this.eventBus.fireAny(message);\n\n        sleep();\n        Assert.assertEquals(6, CustomEvent.myMessageLong.sum());\n    }\n\n    @Test\n    public void testOther() {\n        MyMessage message = new MyMessage();\n        message.setName(\"ok\");\n\n        EventBusMessage eventBusMessage = this.eventBus.createEventBusMessage(message);\n        eventBusMessage.setThreadIndex(1);\n\n        this.eventBus.fire(eventBusMessage);\n//        this.eventBus.fire(message);\n        IoGameBanner.printLine();\n//        this.eventBus.fire(message);\n\n    }\n\n    @Test\n    public void fireMe() {\n        MyMessage message = new MyMessage();\n        message.setName(\"fireMe\");\n\n        this.eventBus.fireMeSync(message);\n\n        Assert.assertEquals(3, CustomEvent.myMessageLong.sum());\n\n        this.eventBus.fireMe(message);\n\n        sleep();\n        Assert.assertEquals(6, CustomEvent.myMessageLong.sum());\n    }\n\n    @Test\n    public void fireLocal() {\n        MyMessage message = new MyMessage();\n        message.setName(\"fireLocal\");\n\n        this.eventBus.fireLocalSync(message);\n        Assert.assertEquals(6, CustomEvent.myMessageLong.sum());\n\n        this.eventBus.fireLocal(message);\n\n        sleep();\n        Assert.assertEquals(12, CustomEvent.myMessageLong.sum());\n    }\n\n    @Test\n    public void fire() {\n        MyMessage message = new MyMessage();\n        message.setName(\"ok\");\n\n        MyRecord myRecord = new MyRecord(100);\n\n        this.eventBus.fireSync(message);\n        this.eventBus.fire(message);\n\n        this.eventBus.fireSync(myRecord);\n        this.eventBus.fire(myRecord);\n\n        sleep();\n        Assert.assertEquals(12, CustomEvent.myMessageLong.sum());\n        Assert.assertEquals(8, CustomEvent.myRecordLong.sum());\n    }\n\n    private void sleep() {\n        try {\n            TimeUnit.MILLISECONDS.sleep(50);\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/eventbus/MyMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\nimport lombok.AccessLevel;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class MyMessage {\n    String name;\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/eventbus/MyRecord.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.eventbus;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-24\n */\npublic record MyRecord(int age) {\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/i18n/BundleTest.java",
    "content": "package com.iohao.game.action.skeleton.i18n;\n\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.Locale;\nimport java.util.ResourceBundle;\n\n/**\n * @author 渔民小镇\n * @date 2024-10-02\n * @since 21.18\n */\npublic class BundleTest {\n\n    @Test\n    public void getMessage() {\n        var b1 = ResourceBundle.getBundle(Bundle.baseName, Locale.getDefault());\n        var b2 = ResourceBundle.getBundle(Bundle.baseName, Locale.getDefault());\n\n        Assert.assertEquals(b1, b2);\n\n        extracted();\n\n        Locale.setDefault(Locale.US);\n        extracted();\n    }\n\n    private static void extracted() {\n        System.out.println(\"------------------\");\n\n        Bundle.bundle = null;\n\n        System.out.println(Locale.getDefault().toString());\n\n        String value = Bundle.getMessage(MessageKey.printActionKitPrintClose);\n        IoGameBanner.printlnMsg(value + \" BarSkeletonBuilder.setting.printRunners\");\n        Assert.assertNotNull(value);\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/protocol/ResponseMessageTest.java",
    "content": "package com.iohao.game.action.skeleton.protocol;\n\nimport com.iohao.game.action.skeleton.protocol.wrapper.*;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2024-06-09\n */\npublic class ResponseMessageTest {\n\n    @Test\n    public void getValue() {\n        ResponseMessage responseMessage = new ResponseMessage();\n\n        Student student = new Student();\n        student.name = \"1\";\n\n        // ------ object ------\n        responseMessage.setData(student);\n        Assert.assertEquals(responseMessage.getValue(Student.class).name, \"1\");\n\n        // ------ object list ------\n        responseMessage.setData(ByteValueList.ofList(List.of(student)));\n\n        List<Student> listValue = responseMessage.listValue(Student.class);\n        Assert.assertEquals(listValue.size(), 1);\n        Assert.assertEquals(listValue.getFirst().name, student.name);\n\n        // ------ int ------\n        responseMessage.setData(IntValue.of(1));\n        Assert.assertEquals(responseMessage.getInt(), 1);\n\n        // ------ int list ------\n        responseMessage.setData(IntValueList.of(List.of(1, 2)));\n\n        List<Integer> listInt = responseMessage.listInt();\n        Assert.assertEquals(listInt.size(), 2);\n        Assert.assertEquals(listInt.getFirst().intValue(), 1);\n        Assert.assertEquals(listInt.get(1).intValue(), 2);\n\n        // ------ long ------\n        responseMessage.setData(LongValue.of(1));\n        Assert.assertEquals(responseMessage.getLong(), 1);\n\n        // ------ long list ------\n        responseMessage.setData(LongValueList.of(List.of(1L, 2L)));\n\n        List<Long> listLong = responseMessage.listLong();\n        Assert.assertEquals(listLong.size(), 2);\n        Assert.assertEquals(listLong.getFirst().longValue(), 1);\n        Assert.assertEquals(listLong.get(1).longValue(), 2);\n\n        // ------ string ------\n        responseMessage.setData(StringValue.of(\"1\"));\n        Assert.assertEquals(responseMessage.getString(), \"1\");\n\n        // ------ string list ------\n        responseMessage.setData(StringValueList.of(List.of(\"1L\", \"2L\")));\n\n        List<String> listString = responseMessage.listString();\n        Assert.assertEquals(listString.size(), 2);\n        Assert.assertEquals(listString.getFirst(), \"1L\");\n        Assert.assertEquals(listString.get(1), \"2L\");\n\n        // ------ boolean ------\n        responseMessage.setData(BoolValue.of(true));\n        Assert.assertTrue(responseMessage.getBoolean());\n\n        // ------ boolean list ------\n        responseMessage.setData(BoolValueList.of(List.of(true, false)));\n\n        List<Boolean> listBoolean = responseMessage.listBoolean();\n        Assert.assertEquals(listBoolean.size(), 2);\n        Assert.assertTrue(listBoolean.getFirst());\n        Assert.assertFalse(listBoolean.get(1));\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/protocol/Student.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.protocol;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2024-06-09\n */\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class Student {\n    /** id */\n    String name;\n}\n"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKitTest.java",
    "content": "package com.iohao.game.action.skeleton.protocol.wrapper;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-09\n */\n@Slf4j\npublic class WrapperKitTest {\n\n    @Test\n    public void of() {\n        IntValue of = WrapperKit.of(1);\n        log.info(\"of : {}\", of);\n\n        int intV = 1;\n        Object of1 = WrapperKit.of(intV);\n        log.info(\"of1 : {}\", of1);\n    }\n\n    @Test\n    public void isWrapper() {\n        Assert.assertTrue(WrapperKit.isWrapper(int.class));\n        Assert.assertTrue(WrapperKit.isWrapper(Integer.class));\n        Assert.assertTrue(WrapperKit.isWrapper(IntValue.class));\n\n        Assert.assertTrue(WrapperKit.isWrapper(long.class));\n        Assert.assertTrue(WrapperKit.isWrapper(Long.class));\n        Assert.assertTrue(WrapperKit.isWrapper(LongValue.class));\n\n        Assert.assertTrue(WrapperKit.isWrapper(boolean.class));\n        Assert.assertTrue(WrapperKit.isWrapper(Boolean.class));\n        Assert.assertTrue(WrapperKit.isWrapper(BoolValue.class));\n\n        Assert.assertTrue(WrapperKit.isWrapper(String.class));\n        Assert.assertTrue(WrapperKit.isWrapper(StringValue.class));\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/action/skeleton/toy/ToyTableTest.java",
    "content": "package com.iohao.game.action.skeleton.toy;\n\nimport com.iohao.game.action.skeleton.IoGameVersion;\nimport org.junit.Test;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-30\n */\npublic class ToyTableTest {\n    @Test\n    public void test() {\n\n        ToyTable table = new ToyTable();\n        ToyTableRegion ioGameRegion = table.getRegion(\"ioGame\");\n        ioGameRegion.putLine(\"pid\", \"73033\");\n        ioGameRegion.putLine(\"version\", IoGameVersion.VERSION);\n        ioGameRegion.putLine(\"document\", \"http://game.iohao.com\");\n\n        ToyTableRegion memoryRegion = table.getRegion(\"Memory\");\n        memoryRegion.putLine(\"used\", \"xx.xxMB\");\n        memoryRegion.putLine(\"freeMemory\", \"xxx.xxMB\");\n        memoryRegion.putLine(\"totalMemory\", \"xxx.xMB\");\n\n        table.render();\n    }\n\n    @Test\n    public void render() throws InterruptedException {\n        IoGameBanner.render();\n        IoGameBanner.me().countDown();\n\n//        TimeUnit.SECONDS.sleep(1);\n        TimeUnit.MILLISECONDS.sleep(300);\n    }\n}"
  },
  {
    "path": "common/common-core/src/test/java/com/iohao/game/common/kit/ClassScannerTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport com.iohao.game.action.skeleton.annotation.ActionController;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport lombok.extern.slf4j.Slf4j;\nimport org.fusesource.jansi.Ansi;\nimport org.junit.Test;\n\nimport java.util.List;\nimport java.util.function.Predicate;\n\n@Slf4j\npublic class ClassScannerTest {\n\n    //    @Test\n    public void scan() {\n        Predicate<Class<?>> predicateFilter = (clazz) -> {\n            ActionController annotation = clazz.getAnnotation(ActionController.class);\n            return annotation != null;\n        };\n\n        String packagePath = \"com.iohao.game\";\n        packagePath = \"com.iohao.game.action.skeleton.core.action\";\n        ClassScanner scanner = new ClassScanner(packagePath, predicateFilter);\n\n        List<Class<?>> classList = scanner.listScan();\n\n        for (Class<?> clazz : classList) {\n            log.info(\"clazz: {}\", clazz);\n        }\n    }\n\n    @Test\n    public void test() {\n        log.info(\"hello ioGame {}\", \"miss\");\n        String title = \"!~ @|CYAN ======================== action ========================= |@ ~!\";\n//        AnsiConsole.systemInstall();\n        IoGameBanner.printlnMsg(\"Hello World\");\n\n        IoGameBanner.println1(Ansi.ansi().eraseScreen().render(title));\n\n        Ansi render = Ansi.ansi().eraseScreen().render(title);\n        IoGameBanner.println1(render.eraseScreen().reset());\n    }\n\n}"
  },
  {
    "path": "common/common-core/src/test/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration debug=\"false\">\n    <!-- 关闭 logback 启动时打印的无效日志 -->\n    <statusListener class=\"ch.qos.logback.core.status.NopStatusListener\"/>\n    <!-- 日志级别 -->\n    <property name=\"log.root.level\" value=\"INFO\"/>\n    <!-- 其他日志级别 -->\n    <property name=\"log.other.level\" value=\"DEBUG\"/>\n    <!-- 模块名称， 影响日志配置名，日志文件名 -->\n    <property name=\"log.moduleName\" value=\"game\"/>\n    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其；否则，在当前目录下创建名为logs目录做日志存放的目录 -->\n    <property name=\"log.base\" value=\"${log.dir:-logs}/${log.moduleName}\"/>\n    <property name=\"log.max.size\" value=\"100MB\"/> <!-- 日志文件大小,超过这个大小将被压缩 -->\n\n    <!-- 彩色日志 -->\n    <property name=\"log.pattern\"\n              value=\"%d{HH:mm:ss.SSS} %green([%thread]) %highlight(%-5level) %cyan(%logger{5}).%M\\(%F:%L\\) %m%n\"/>\n    <!--控制台输出 -->\n    <appender name=\"stdout\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <!--格式化输出：%d表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度%msg：日志消息，%n是换行符-->\n            <!--            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %highlight(%msg) %n</pattern>-->\n            <pattern>${log.pattern}</pattern>\n            <charset>utf8</charset>\n        </encoder>\n    </appender>\n\n    <!-- 用来保存输出所有级别的日志 -->\n    <appender name=\"file.all\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <File>${log.base}/${log.moduleName}.log</File><!-- 设置日志不超过${log.max.size}时的保存路径，注意如果\n            是web项目会保存到Tomcat的bin目录 下 -->\n        <!-- 滚动记录文件，先将日志记录到指定文件，当符合某个条件时，将日志记录到其他文件。 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <FileNamePattern>${log.base}/archive/${log.moduleName}_all_%d{yyyy-MM-dd}.%i.log.zip</FileNamePattern>\n            <!-- 文件输出日志 (文件大小策略进行文件输出，超过指定大小对文件备份) -->\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>${log.max.size}</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n        </rollingPolicy>\n        <!-- 日志输出的文件的格式 -->\n        <layout class=\"ch.qos.logback.classic.PatternLayout\">\n            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{80}.%method:%L -%msg%n</pattern>\n        </layout>\n    </appender>\n\n    <!-- 这也是用来保存输出所有级别的日志 -->\n    <appender name=\"file.all.other\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <File>${log.base}/${log.moduleName}_other.log</File>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <FileNamePattern>${log.base}/archive/${log.moduleName}_other_%d{yyyy-MM-dd}.%i.log.zip\n            </FileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>${log.max.size}</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n        </rollingPolicy>\n        <layout class=\"ch.qos.logback.classic.PatternLayout\">\n            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{56}.%method:%L -%msg%n</pattern>\n        </layout>\n    </appender>\n\n    <!-- 只用保存输出error级别的日志 -->\n    <appender name=\"file.error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <File>${log.base}/${log.moduleName}_err.log</File>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <FileNamePattern>${log.base}/archive/${log.moduleName}_err_%d{yyyy-MM-dd}.%i.log.zip\n            </FileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>${log.max.size}</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n        </rollingPolicy>\n        <layout class=\"ch.qos.logback.classic.PatternLayout\">\n            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{56}.%method:%L - %msg%n</pattern>\n        </layout>\n        <!-- 下面为配置只输出error级别的日志 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>ERROR</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->\n    <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->\n    <!-- 添加附加的appender,最多只能添加一个 -->\n    <appender name=\"file.async\" class=\"ch.qos.logback.classic.AsyncAppender\">\n        <discardingThreshold>0</discardingThreshold>\n        <queueSize>256</queueSize>\n        <includeCallerData>true</includeCallerData>\n        <appender-ref ref=\"file.all\"/>\n    </appender>\n\n    　　<!-- 使用异步来记录其他信息-->\n    <appender name=\"file.async.other\" class=\"ch.qos.logback.classic.AsyncAppender\">\n        <discardingThreshold>0</discardingThreshold>\n        <queueSize>256</queueSize>\n        <includeCallerData>true</includeCallerData>\n        <appender-ref ref=\"file.all.other\"/>\n    </appender>\n\n    <!-- 为某个包下的所有类的指定Appender 这里也可以指定类名称例如：com.aa.bb.ClassName -->\n    <logger name=\"com.iohao.game\" additivity=\"false\">\n        <level value=\"${log.root.level}\"/>\n        <appender-ref ref=\"stdout\"/>\n        <appender-ref ref=\"file.async\"/><!-- 即com.iohao.game包下级别为 ${log.root.level}的才会使用file.async来打印 -->\n        <appender-ref ref=\"file.error\"/>\n    </logger>\n\n    <!-- root将级别为${log.root.level}及大于${log.root.level}的日志信息交给已经配置好的名为“Console”的appender处理，“Console”appender将信息打印到Console,其它同理 -->\n    <root level=\"${log.root.level}\">\n        <appender-ref ref=\"stdout\"/> <!--  标识这个appender将会添加到这个logger -->\n        <appender-ref ref=\"file.async.other\"/>\n        <appender-ref ref=\"file.error\"/>\n    </root>\n</configuration>\n"
  },
  {
    "path": "common/common-kit/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>common-kit</artifactId>\n    <name>common-kit for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>common-micro-kit</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/com.esotericsoftware/reflectasm -->\n        <dependency>\n            <groupId>com.esotericsoftware</groupId>\n            <artifactId>reflectasm</artifactId>\n            <version>${reflectasm.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-classic</artifactId>\n            <version>${logback.version}</version>\n            <optional>true</optional>\n        </dependency>\n\n    </dependencies>\n</project>"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/ProtoKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport com.baidu.bjf.remoting.protobuf.Codec;\nimport com.baidu.bjf.remoting.protobuf.ProtobufProxy;\nimport com.iohao.game.common.consts.CommonConst;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-11\n */\n@UtilityClass\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class ProtoKit {\n    /**\n     * 将对象转为 pb 字节数组\n     *\n     * @param data 对象\n     * @return 字节数组 （一定不为null）\n     */\n    @SuppressWarnings(\"unchecked\")\n    public byte[] toBytes(Object data) {\n\n        if (Objects.isNull(data)) {\n            return CommonConst.emptyBytes;\n        }\n\n        Class clazz = data.getClass();\n        Codec<Object> codec = ProtobufProxy.create(clazz);\n\n        try {\n            return codec.encode(data);\n        } catch (Throwable e) {\n            log.error(e.getMessage(), e);\n        }\n\n        return CommonConst.emptyBytes;\n    }\n\n    /**\n     * 将字节解析成 pb 对象\n     *\n     * @param data  pb 字节\n     * @param clazz pb class\n     * @param <T>   t\n     * @return pb 对象\n     */\n    public <T> T parseProtoByte(byte[] data, Class<T> clazz) {\n\n        if (Objects.isNull(data)) {\n            return null;\n        }\n\n        Codec<T> codec = ProtobufProxy.create(clazz);\n\n        try {\n            return codec.decode(data);\n        } catch (Throwable e) {\n            log.error(e.getMessage(), e);\n        }\n\n        return null;\n    }\n\n    public void create(Class<?> clazz) {\n        TaskKit.executeVirtual(() -> {\n            // create a protobuf proxy class\n            ProtobufProxy.create(clazz);\n        });\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/asm/ClassRefInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.asm;\n\nimport com.esotericsoftware.reflectasm.ConstructorAccess;\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.Map;\n\n\n/**\n * class asm相关信息\n * <pre>\n *     创建对象\n *     获取方法\n *     获取字段\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Getter\n@ToString\n@SuppressWarnings(\"unchecked\")\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic final class ClassRefInfo implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -4297558765639660029L;\n    /** 类信息 */\n    Class<?> clazz;\n    /** 以属性名作为key */\n    Map<String, FieldRefInfo> filedRefInfoMap;\n    /** 构造 访问器 */\n    ConstructorAccess<?> constructorAccess;\n    /** 方法 访问器 */\n    MethodAccess methodAccess;\n    /** 以方法名作为key */\n    Map<String, MethodRefInfo> methodRefInfoMap;\n\n    ClassRefInfo() {\n    }\n\n    /**\n     * 创建构建器\n     *\n     * @return 构建器\n     */\n    static ClassRefInfoBuilder newBuilder() {\n        return new ClassRefInfoBuilder();\n    }\n\n    /**\n     * 创建一个实例\n     *\n     * @param <T> t\n     * @return 对象\n     */\n    public <T> T newInstance() {\n        return (T) this.constructorAccess.newInstance();\n    }\n\n    /**\n     * 字段反射信息\n     *\n     * @param filedName 字段名\n     * @return 字段信息\n     */\n    public FieldRefInfo getFieldRefInfo(String filedName) {\n        return this.filedRefInfoMap.get(filedName);\n    }\n\n    /**\n     * 方法反射信息\n     *\n     * @param methodName 方法名\n     * @return 方法信息\n     */\n    public MethodRefInfo getMethodRefInfo(String methodName) {\n        return this.methodRefInfoMap.get(methodName);\n    }\n\n    /**\n     * 执行方法\n     *\n     * @param object     业务对象\n     * @param methodName 方法名\n     * @param args       参数\n     * @return 返回值\n     */\n    public Object invokeMethod(Object object, String methodName, Object args) {\n        MethodRefInfo methodRefInfo = getMethodRefInfo(methodName);\n        return methodRefInfo.invokeMethod(object, args);\n    }\n\n    /**\n     * 从对象中获取字段属性值\n     *\n     * @param object    对象\n     * @param filedName 字段名\n     * @param <T>       t\n     * @return 字段属性值\n     */\n\n    public <T> T invokeGetter(Object object, String filedName) {\n        FieldRefInfo filedRefInfo = filedRefInfoMap.get(filedName);\n\n        String methodName = filedRefInfo.getMethodGetName();\n        MethodRefInfo methodRefInfo = getMethodRefInfo(methodName);\n\n        return (T) methodRefInfo.invokeMethod(object);\n    }\n\n    /**\n     * 设置对象中的字段属性值\n     *\n     * @param object    对象\n     * @param filedName 字段名\n     * @param value     设置的值\n     * @return me\n     */\n    public ClassRefInfo invokeSetter(Object object, String filedName, Object value) {\n        FieldRefInfo filedRefInfo = filedRefInfoMap.get(filedName);\n\n        String methodName = filedRefInfo.getMethodSetName();\n        MethodRefInfo methodRefInfo = getMethodRefInfo(methodName);\n\n        methodRefInfo.invokeMethod(object, value);\n        return this;\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/asm/ClassRefInfoBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.asm;\n\nimport com.esotericsoftware.reflectasm.ConstructorAccess;\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\nclass ClassRefInfoBuilder {\n    Class<?> clazz;\n    /** 构造 访问器 */\n    ConstructorAccess<?> constructorAccess;\n    /** 方法 访问器 */\n    MethodAccess methodAccess;\n\n    ClassRefInfo build() {\n        ClassRefInfo classRefInfo = new ClassRefInfo();\n        classRefInfo.constructorAccess = this.constructorAccess;\n        classRefInfo.methodAccess = this.methodAccess;\n        classRefInfo.clazz = this.clazz;\n\n        // method - 以方法名作为 key\n        classRefInfo.methodRefInfoMap = createMethodMap();\n\n        // field - 以属性名作为 key\n        classRefInfo.filedRefInfoMap = createFieldMap();\n\n        return classRefInfo;\n    }\n\n    private Map<String, FieldRefInfo> createFieldMap() {\n        List<Field> fieldList = listField();\n        // list 转 map, 属性名作为key\n        return fieldList.stream().collect(Collectors.toMap(Field::getName, field -> {\n            // 开放访问权限\n            field.setAccessible(true);\n\n            // 属性名\n            String fieldName = field.getName();\n\n            // 首字母转换为大写\n            fieldName = StrKit.firstCharToUpperCase(fieldName);\n\n            // method 对应的 getter setter\n            String methodGetName = \"get\" + fieldName;\n            String methodSetName = \"set\" + fieldName;\n\n            FieldRefInfo filedRefInfo = new FieldRefInfo();\n            filedRefInfo.fieldName = fieldName;\n\n            // 方法访问下标\n            filedRefInfo.methodGetIndex = this.methodAccess.getIndex(methodGetName);\n            filedRefInfo.methodSetIndex = this.methodAccess.getIndex(methodSetName);\n\n            // 方法名\n            filedRefInfo.methodGetName = methodGetName;\n            filedRefInfo.methodSetName = methodSetName;\n\n            // 返回值\n            filedRefInfo.returnType = field.getType();\n\n            // 原生 field\n            filedRefInfo.field = field;\n\n            return filedRefInfo;\n        }));\n    }\n\n    private Map<String, MethodRefInfo> createMethodMap() {\n        List<Method> methodList = listMethod();\n        // list 转 map, 方法名作为 key\n        return methodList.stream().collect(Collectors.toMap(Method::getName, method -> {\n            // 方法名, 方法下标\n            String methodName = method.getName();\n            int methodIndex = this.methodAccess.getIndex(methodName);\n\n            MethodRefInfo methodRefInfo = new MethodRefInfo();\n            methodRefInfo.methodAccess = this.methodAccess;\n            methodRefInfo.method = method;\n            methodRefInfo.methodIndex = methodIndex;\n            methodRefInfo.methodName = methodName;\n            return methodRefInfo;\n        }));\n\n    }\n\n    /**\n     * 获取类的字段列表\n     * <pre>\n     *     包含父类的字段\n     * </pre>\n     *\n     * @return 字段列表\n     */\n    private List<Field> listField() {\n        // 类信息\n        Class<?> nextClass = this.clazz;\n        // bean 字段列表\n        List<Field> fieldList = new ArrayList<>();\n\n        // 获取类的字段, 并包含父类的\n        while (nextClass != Object.class) {\n            Field[] declaredFields = nextClass.getDeclaredFields();\n            for (Field field : declaredFields) {\n                int modifiers = field.getModifiers();\n                // 静态的字段不需要\n                if (Modifier.isStatic(modifiers)) {\n                    continue;\n                }\n                fieldList.add(field);\n            }\n            // 父类 class\n            nextClass = nextClass.getSuperclass();\n        }\n\n        return fieldList;\n    }\n\n    /**\n     * 获取类的方法列表\n     * <pre>\n     *     包含父类的方法\n     * </pre>\n     *\n     * @return 方法列表\n     */\n    private List<Method> listMethod() {\n        // 类信息\n        Class<?> nextClass = this.clazz;\n        // bean 方法列表\n        List<Method> methodList = new ArrayList<>();\n\n        // 获取类的字段, 并包含父类的\n        while (nextClass != Object.class) {\n            Method[] declaredMethods = nextClass.getDeclaredMethods();\n            for (Method method : declaredMethods) {\n                int modifiers = method.getModifiers();\n                // 静态的字段不需要\n                if (Modifier.isStatic(modifiers) || method.isBridge()) {\n                    continue;\n                }\n                methodList.add(method);\n            }\n            // 父类 class\n            nextClass = nextClass.getSuperclass();\n        }\n\n        return methodList;\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/asm/ClassRefInfoKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.asm;\n\nimport com.esotericsoftware.reflectasm.ConstructorAccess;\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport com.iohao.game.common.kit.MoreKit;\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 类信息工具\n * <pre>\n *     包含了反射等信息\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@UtilityClass\npublic class ClassRefInfoKit {\n    /**\n     * key: 类信息\n     * value: 类反射信息\n     */\n    private final Map<Class<?>, ClassRefInfo> classRefInfoMap = new NonBlockingHashMap<>();\n\n    /**\n     * 获取类信息\n     *\n     * @param clazz class\n     * @return 类信息\n     */\n    public ClassRefInfo getClassRefInfo(Class<?> clazz) {\n        ClassRefInfo classRefInfo = classRefInfoMap.get(clazz);\n\n        // 无锁化\n        if (Objects.isNull(classRefInfo)) {\n            ClassRefInfo newValue = createClassRefInfo(clazz);\n            return MoreKit.putIfAbsent(classRefInfoMap, clazz, newValue);\n        }\n\n        return classRefInfo;\n    }\n\n    /**\n     * 创建类的反射信息\n     *\n     * @param clazz 类信息\n     * @return 类反射信息\n     */\n    private ClassRefInfo createClassRefInfo(Class<?> clazz) {\n        // 构造函数访问器\n        ConstructorAccess<?> constructorAccess = ConstructorAccess.get(clazz);\n        // 方法访问器\n        MethodAccess methodAccess = MethodAccess.get(clazz);\n\n        return ClassRefInfo.newBuilder()\n                .setClazz(clazz)\n                .setConstructorAccess(constructorAccess)\n                .setMethodAccess(methodAccess)\n                .build();\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/asm/FieldRefInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.asm;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.lang.reflect.Field;\n\n/**\n * 字段信息 - 对应类的成员属性\n * <pre>\n *    name\n *    age\n * </pre>\n * <pre>\n *     不能提供私有属性的直接方法\n *     属性必须拥有 getter setter\n *     如果要调用私有属性, 使用 Field\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@ToString\n@Getter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic class FieldRefInfo implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -5717006947739357125L;\n    /** 字段名 */\n    String fieldName;\n    /** 返回值类型 */\n    Class<?> returnType;\n    /** 字段对应的 get 方法下标 */\n    int methodGetIndex;\n    /** 字段对应的 set 方法下标 */\n    int methodSetIndex;\n    /** 字段对应的 get 方法名 */\n    String methodGetName;\n    /** 字段对应的 set 方法名 */\n    String methodSetName;\n    /** 原生字段 */\n    Field field;\n}"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/asm/MethodRefInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.asm;\n\nimport com.esotericsoftware.reflectasm.MethodAccess;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.lang.reflect.Method;\n\n/**\n * 类方法信息\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic class MethodRefInfo {\n\n    /** 方法下标 */\n    int methodIndex;\n    /** 方法名 */\n    String methodName;\n    /** 原生方法对象 */\n    Method method;\n    /** 方法 访问器 */\n    MethodAccess methodAccess;\n\n    MethodRefInfo() {\n    }\n\n    /**\n     * 执行方法\n     *\n     * @param object 业务对象\n     * @param args   方法参数\n     * @return 返回值\n     */\n    public Object invokeMethod(Object object, Object args) {\n        return methodAccess.invoke(object, methodIndex, args);\n    }\n\n    /**\n     * 执行无参方法\n     *\n     * @param object 业务对象\n     * @return 返回值\n     */\n    public Object invokeMethod(Object object) {\n        return methodAccess.invoke(object, methodIndex);\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/asm/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - asm\n * <pre>\n *     使用示例:\n *     ClassRefInfo classRefInfo = ClassRefInfoKit.getClassRefInfo(Your.class);\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\npackage com.iohao.game.common.kit.asm;"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/io/FileKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.io;\n\nimport com.iohao.game.common.kit.adapter.AdapterHuUtils;\nimport lombok.experimental.UtilityClass;\n\nimport java.io.File;\n\n/**\n * @author 渔民小镇\n * @date 2022-12-23\n */\n@UtilityClass\npublic class FileKit {\n    /**\n     * 创建文件夹，如果存在直接返回此文件夹<br>\n     * 此方法不对 File 对象类型做判断，如果 File 不存在，无法判断其类型\n     *\n     * @param dirPath 文件夹路径，使用 POSIX 格式，无论哪个平台\n     * @return 创建的目录\n     */\n    public File mkdir(String dirPath) {\n        return AdapterHuUtils.mkdir(dirPath);\n    }\n\n    /**\n     * 创建 File 对象，自动识别相对或绝对路径，相对路径将自动从 ClassPath 下寻找\n     *\n     * @param path 相对 ClassPath 的目录或者绝对路径目录\n     * @return File\n     */\n    public File file(String path) {\n        return AdapterHuUtils.file(path);\n    }\n\n    /**\n     * 将String写入文件，覆盖模式，字符集为UTF-8\n     *\n     * @param content 写入的内容\n     * @param path    文件路径\n     * @return 写入的文件\n     */\n    public File writeUtf8String(String content, String path) {\n        return AdapterHuUtils.writeUtf8String(content, path);\n    }\n\n    /**\n     * 判断是否为目录，如果 path 为 null，则返回 false\n     *\n     * @param path 文件路径\n     * @return 如果为目录 true\n     */\n    public boolean isDirectory(String path) {\n        return AdapterHuUtils.isDirectory(path);\n    }\n\n    /**\n     * 判断文件是否存在，如果 file 为 null，则返回 false\n     *\n     * @param file 文件\n     * @return 如果存在返回 true\n     */\n    public static boolean exist(File file) {\n        return AdapterHuUtils.exist(file);\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/io/ResourceKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.io;\n\nimport com.iohao.game.common.kit.adapter.AdapterHuUtils;\nimport lombok.experimental.UtilityClass;\n\nimport java.nio.charset.Charset;\n\n/**\n * @author 渔民小镇\n * @date 2022-12-23\n */\n@UtilityClass\npublic class ResourceKit {\n    public static String readStr(String resource, Charset charset) {\n        return AdapterHuUtils.readStr(resource, charset);\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/io/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - io\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.kit.io;"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/system/InternalSystemPropsKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.system;\n\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * copy from hutool\n *\n * @author 渔民小镇\n * @date 2023-01-19\n */\n@UtilityClass\n@Slf4j(topic = IoGameLogName.CommonStdout)\nclass InternalSystemPropsKit {\n\n    /**\n     * 取得系统属性，如果因为 Java 安全的限制而失败，则将错误打在 Log 中，然后返回 {@code null}\n     *\n     * @param name  属性名\n     * @param quiet 安静模式，不将出错信息打在{@code System.err}中\n     * @return 属性值或{@code null}\n     * @see System#getProperty(String)\n     * @see System#getenv(String)\n     */\n    public static String get(String name, boolean quiet) {\n        String value = null;\n        try {\n            value = System.getProperty(name);\n        } catch (SecurityException e) {\n            if (!quiet) {\n                log.error(\"Caught a SecurityException reading the system property '{}'; the SystemPropsKit property value will default to null.\", name);\n            }\n        }\n\n        if (null == value) {\n            try {\n                value = System.getenv(name);\n            } catch (SecurityException e) {\n                if (!quiet) {\n                    log.error(\"Caught a SecurityException reading the system env '{}'; the SystemPropsKit env value will default to null.\", name);\n                }\n            }\n        }\n\n        return value;\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/system/OsInfo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.system;\n\nimport lombok.Getter;\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n@UtilityClass\npublic class OsInfo {\n    @Getter\n    final String osName = InternalSystemPropsKit.get(\"os.name\", false);\n    @Getter\n    final boolean linux = getOsMatches(\"Linux\") || getOsMatches(\"LINUX\");\n    @Getter\n    final boolean mac = getOsMatches(\"Mac\") || getOsMatches(\"Mac OS X\");\n\n    private boolean getOsMatches(String osNamePrefix) {\n        if (osName == null) {\n            return false;\n        }\n\n        return osName.startsWith(osNamePrefix);\n    }\n}\n"
  },
  {
    "path": "common/common-kit/src/main/java/com/iohao/game/common/kit/system/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - system\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.kit.system;"
  },
  {
    "path": "common/common-micro-kit/README.md",
    "content": "```java\nopen module com.iohao.game.common.micro.kit {\n    requires transitive lombok;\n    requires transitive org.slf4j;\n    requires transitive jctools.core;\n\n    requires transitive com.iohao.game.common.kit.other.tool;\n\n    exports com.iohao.game.common.consts;\n    exports com.iohao.game.common.internal;\n\n    exports com.iohao.game.common.kit;\n    exports com.iohao.game.common.kit.attr;\n    exports com.iohao.game.common.kit.micro.room;\n    exports com.iohao.game.common.kit.weight;\n}\n```"
  },
  {
    "path": "common/common-micro-kit/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.iohao.game</groupId>\n        <artifactId>ioGame</artifactId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n\n    <artifactId>common-micro-kit</artifactId>\n    <name>common-micro-kit for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>other-tool</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/org.jctools/jctools-core -->\n        <dependency>\n            <groupId>org.jctools</groupId>\n            <artifactId>jctools-core</artifactId>\n            <version>${jctools-core.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/io.netty/netty-common -->\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-common</artifactId>\n            <version>${netty.version}</version>\n        </dependency>\n\n    </dependencies>\n</project>"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/consts/CommonConst.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.consts;\n\n/**\n * 公共常量\n *\n * @author 渔民小镇\n * @date 2023-05-01\n */\npublic interface CommonConst {\n    byte[] emptyBytes = new byte[0];\n    int[] emptyInt = new int[0];\n    Object[] emptyObjects = new Object[0];\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/consts/IoGameLogName.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.consts;\n\n/**\n * @author 渔民小镇\n * @date 2023-08-05\n */\npublic interface IoGameLogName {\n    /** CommonStdout */\n    String CommonStdout = \"CommonStdout\";\n    String ExternalTopic = \"ExternalTopic\";\n    String ClusterTopic = \"ClusterTopic\";\n    String MsgTransferTopic = \"MsgTransferTopic\";\n    String ConnectionTopic = \"ConnectionTopic\";\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/internal/BootConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.internal;\n\n/**\n * Boot 配置\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface BootConfig {\n    /**\n     * 加载配置\n     */\n    void config();\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/internal/BootItemConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.internal;\n\n/**\n * BootItem 配置项\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\npublic interface BootItemConfig {\n\n    /**\n     * 配置初始化\n     */\n    void config();\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/internal/BootItemConfigKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.internal;\n\n\nimport com.iohao.game.common.kit.ClassScanner;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.function.Predicate;\n\n/**\n * BootItem 配置项工具\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@Slf4j\npublic class BootItemConfigKit {\n    /**\n     * 加载配置\n     * <pre>\n     *     这里参数使用类,而没有使用 str 的包路径是因为:\n     *     如果 str 所在的配置项变更了路径, 会导致这里加载不到(除非手动改这个 str, 毕竟有时会忘记改参数)\n     *     所以这里使用类来当做参数, 毕竟类是一定存在的, str 的包缺不一定存在, 相当于做一个代码级别的验证.\n     * </pre>\n     *\n     * @param packClazz 任意 class 都可以, 会扫描该 class 包下面的所有 class, 并加载\n     */\n    public static void loadBootItemConfig(Class<? extends BootItemConfig> packClazz) {\n        // 过滤条件\n        Predicate<Class<?>> predicate = BootItemConfig.class::isAssignableFrom;\n        // 扫描路径\n        String packagePath = packClazz.getPackageName();\n        ClassScanner classScanner = new ClassScanner(packagePath, predicate);\n\n        List<Class<?>> classList = classScanner.listScan();\n        for (Class<?> clazz : classList) {\n            try {\n                BootItemConfig itemConfig = (BootItemConfig) clazz.getDeclaredConstructor().newInstance();\n                itemConfig.config();\n            } catch (Exception e) {\n                log.error(e.getMessage(), e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/AboutKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n@UtilityClass\nclass OperationCodeKit {\n    final AtomicInteger codeAtomic = new AtomicInteger(1);\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/ArrayKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * 数组相关工具\n *\n * @author 渔民小镇\n * @date 2022-01-14\n */\n@UtilityClass\npublic class ArrayKit {\n\n    /**\n     * 统计数组数量\n     *\n     * @return 数量\n     */\n    public int sum(int[] cards) {\n        return Arrays.stream(cards).sum();\n    }\n\n    public int[] copy(int[] cards) {\n        int length = cards.length;\n        int[] copyCards = new int[length];\n        System.arraycopy(cards, 0, copyCards, 0, length);\n        return copyCards;\n    }\n\n    public List<Integer> toList(int[] cards) {\n        List<Integer> list = new ArrayList<>();\n        for (int i = 0; i < cards.length; i++) {\n            int num = cards[i];\n            if (num == 0) {\n                continue;\n            }\n\n            for (int j = 0; j < num; j++) {\n                list.add(i);\n            }\n        }\n\n        return list;\n    }\n\n    public void subtract(int[] cards, int[] beCards) {\n        for (int i = 0; i < cards.length; i++) {\n            cards[i] = cards[i] - beCards[i];\n        }\n    }\n\n    public void plus(int[] cards, int[] beCards) {\n        for (int i = 0; i < cards.length; i++) {\n            cards[i] = cards[i] + beCards[i];\n        }\n    }\n\n    public void plus(int[] cards, List<Integer> beCards) {\n        for (Integer value : beCards) {\n            cards[value]++;\n        }\n    }\n\n    public List<Integer> random(int[] cards, int size) {\n        List<Integer> list = toList(cards);\n        list = list.subList(0, size);\n        return list;\n    }\n\n    public String join(Object[] array, CharSequence delimiter) {\n        return Arrays.stream(array)\n                .map(Object::toString)\n                .collect(Collectors.joining(delimiter));\n    }\n\n    public boolean notEmpty(Object[] array) {\n        return array != null && array.length != 0;\n    }\n\n    public boolean notEmpty(byte[] array) {\n        return !isEmpty(array);\n    }\n\n    public boolean isEmpty(byte[] array) {\n        return array == null || array.length == 0;\n    }\n\n    public boolean isEmpty(int[] array) {\n        return array == null || array.length == 0;\n    }\n\n    public boolean isEmpty(Object[] array) {\n        return array == null || array.length == 0;\n    }\n\n    public <T> T random(T[] array) {\n        int i = RandomKit.randomInt(array.length);\n        return array[i];\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/BaseTypeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Set;\n\n/**\n * 基础类型相关工具\n *\n * @author 渔民小镇\n * @date 2022-01-16\n */\n@UtilityClass\npublic class BaseTypeKit {\n    /** 基础类型 */\n    private Set<Class<?>> baseTypeSet = Set.of(\n            Byte.class,\n            Short.class,\n            Integer.class,\n            Long.class,\n            Character.class,\n            Double.class,\n            Float.class,\n            String.class\n    );\n\n\n    /**\n     * 验证该对象是否基础类型\n     * <pre>\n     *     基础类型包括:\n     *             Byte.class,\n     *             Short.class,\n     *             Integer.class,\n     *             Long.class,\n     *             Character.class,\n     *             Double.class,\n     *             Float.class,\n     *             String.class\n     * </pre>\n     *\n     * @param value 验证对象\n     * @return true 是基础类型\n     */\n    public boolean isBaseType(Object value) {\n        Class<?> beanClass = value.getClass();\n        return baseTypeSet.contains(beanClass);\n    }\n\n    /**\n     * 验证该对象是否基础类型\n     * <pre>\n     *     基础类型包括:\n     *             Byte.class,\n     *             Short.class,\n     *             Integer.class,\n     *             Long.class,\n     *             Character.class,\n     *             Double.class,\n     *             Float.class,\n     *             String.class\n     * </pre>\n     *\n     * @param beanClass 验证 class\n     * @return true 是基础类型\n     */\n    public boolean isBaseType(Class<?> beanClass) {\n        return baseTypeSet.contains(beanClass);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/ByteKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n * @since 21.15\n */\n@UtilityClass\npublic class ByteKit {\n    /**\n     * 将 long 值转换为 byte 数组\n     *\n     * @param value value\n     * @return byte[]\n     */\n    public byte[] toBytes(long value) {\n        byte[] b = new byte[8];\n        b[7] = (byte) (value & 0xff);\n        b[6] = (byte) (value >> 8 & 0xff);\n        b[5] = (byte) (value >> 16 & 0xff);\n        b[4] = (byte) (value >> 24 & 0xff);\n        b[3] = (byte) (value >> 32 & 0xff);\n        b[2] = (byte) (value >> 40 & 0xff);\n        b[1] = (byte) (value >> 48 & 0xff);\n        b[0] = (byte) (value >> 56 & 0xff);\n        return b;\n    }\n\n    /**\n     * get long value\n     *\n     * @param array byte[]\n     * @return long\n     */\n    public long getLong(byte[] array) {\n        return ((((long) array[0] & 0xff) << 56)\n                | (((long) array[1] & 0xff) << 48)\n                | (((long) array[2] & 0xff) << 40)\n                | (((long) array[3] & 0xff) << 32)\n                | (((long) array[4] & 0xff) << 24)\n                | (((long) array[5] & 0xff) << 16)\n                | (((long) array[6] & 0xff) << 8)\n                | (((long) array[7] & 0xff)));\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/ClassScanner.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.*;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.function.Predicate;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\n\n/**\n * class 扫描\n *\n * @author 渔民小镇\n * @date 2021-12-12\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class ClassScanner {\n    /** 需要扫描的包名 */\n    final String packagePath;\n    /** 存放扫描过的 clazz */\n    final Set<Class<?>> clazzSet = new NonBlockingHashSet<>();\n    /** true 保留符合条件的class */\n    final Predicate<Class<?>> predicateFilter;\n\n    ClassLoader classLoader;\n\n    /**\n     * 扫描\n     *\n     * @param packagePath     扫描路径\n     * @param predicateFilter 过滤条件\n     */\n    public ClassScanner(String packagePath, Predicate<Class<?>> predicateFilter) {\n        this.predicateFilter = predicateFilter;\n\n        var path = packagePath.replace('.', '/');\n        path = path.endsWith(\"/\") ? path : path + '/';\n\n        this.packagePath = path;\n    }\n\n    public List<Class<?>> listScan() {\n        try {\n            this.initClassLoad();\n\n            Enumeration<URL> urlEnumeration = classLoader.getResources(packagePath);\n\n            while (urlEnumeration.hasMoreElements()) {\n                URL url = urlEnumeration.nextElement();\n                String protocol = url.getProtocol();\n\n                if (\"jar\".equals(protocol)) {\n                    scanJar(url);\n                } else if (\"file\".equals(protocol)) {\n                    scanFile(url);\n                }\n            }\n\n        } catch (IOException e) {\n            ThrowKit.ofRuntimeException(e);\n        }\n\n        return new ArrayList<>(clazzSet);\n    }\n\n    private void initClassLoad() {\n        if (Objects.nonNull(this.classLoader)) {\n            return;\n        }\n\n        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n        this.classLoader = classLoader != null ? classLoader : ClassScanner.class.getClassLoader();\n    }\n\n    public List<URL> listResource() throws IOException {\n        this.initClassLoad();\n\n        List<URL> list = new ArrayList<>();\n        Set<URI> uriSet = new HashSet<>();\n\n        Enumeration<URL> urlEnumeration = classLoader.getResources(packagePath);\n        while (urlEnumeration.hasMoreElements()) {\n            URL url = urlEnumeration.nextElement();\n\n            try {\n                URI uri = url.toURI();\n                if (uriSet.contains(uri)) {\n                    continue;\n                }\n\n                uriSet.add(uri);\n                list.add(url);\n            } catch (URISyntaxException e) {\n                log.error(e.getMessage(), e);\n            }\n        }\n\n        return list;\n    }\n\n    private void scanJar(URL url) throws IOException {\n        URLConnection urlConn = url.openConnection();\n\n        if (urlConn instanceof JarURLConnection jarUrlConn) {\n            JarFile jarFile = jarUrlConn.getJarFile();\n\n            Enumeration<JarEntry> entries = jarFile.entries();\n            while (entries.hasMoreElements()) {\n                JarEntry entry = entries.nextElement();\n                // jarEntryName\n                String jarEntryName = entry.getName();\n\n                if (jarEntryName.charAt(0) == '/') {\n                    jarEntryName = jarEntryName.substring(1);\n                }\n\n                if (entry.isDirectory() || !jarEntryName.startsWith(packagePath)) {\n                    continue;\n                }\n\n                // 扫描 packagePath 下的类\n                if (jarEntryName.endsWith(\".class\") && jarEntryName.startsWith(packagePath)) {\n                    jarEntryName = jarEntryName.substring(0, jarEntryName.length() - 6).replace('/', '.');\n                    loadClass(jarEntryName);\n                }\n            }\n        }\n    }\n\n    private void scanFile(URL url) {\n        String name = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);\n        File file = new File(name);\n\n        String classPath = getClassPath(file);\n        scanFile(file, classPath);\n    }\n\n    private void scanFile(File file, String classPath) {\n        if (file.isDirectory()) {\n\n            File[] files = file.listFiles();\n\n            if (Objects.isNull(files)) {\n                return;\n            }\n\n            for (File value : files) {\n                scanFile(value, classPath);\n            }\n\n        } else if (file.isFile()) {\n\n            String absolutePath = file.getAbsolutePath();\n\n            if (absolutePath.endsWith(\".class\")) {\n\n                String className = absolutePath\n                        .substring(classPath.length(), absolutePath.length() - 6)\n                        .replace(File.separatorChar, '.');\n\n                loadClass(className);\n            }\n        }\n    }\n\n    private String getClassPath(File file) {\n        String absolutePath = file.getAbsolutePath();\n\n        if (!absolutePath.endsWith(File.separator)) {\n            absolutePath = absolutePath + File.separator;\n        }\n\n        String ret = packagePath.replace('/', File.separatorChar);\n\n        int index = absolutePath.lastIndexOf(ret);\n\n        if (index != -1) {\n            absolutePath = absolutePath.substring(0, index);\n        }\n\n        return absolutePath;\n    }\n\n    private void loadClass(String className) {\n        Class<?> clazz = null;\n\n        try {\n            clazz = classLoader.loadClass(className);\n        } catch (Throwable e) {\n            log.error(e.getMessage(), e);\n        }\n\n        if (clazz != null && !clazzSet.contains(clazz)) {\n            if (predicateFilter.test(clazz)) {\n                clazzSet.add(clazz);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/CollKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * 集合相关工具\n *\n * @author 渔民小镇\n * @date 2022-01-14\n */\n@UtilityClass\npublic class CollKit {\n    /**\n     * 分组统计\n     * <pre>\n     *     key is 元素下标\n     *     value is 元素下标对应的数量\n     * </pre>\n     *\n     * <pre>\n     *     示例\n     *     handCards: [11, 11, 11, 21, 46, 33,33, 18, 18, 18, 18]\n     *\n     *     得到的 map {\n     *         11 : 3\n     *         18 : 4\n     *         21 : 1\n     *         33 : 2\n     *         46 : 1\n     *     }\n     * </pre>\n     *\n     * @param list 元素列表\n     * @return map\n     */\n    public Map<Integer, Integer> groupCounting(List<Integer> list) {\n        return list.stream().\n                collect(\n                        Collectors.groupingBy(Function.identity(), Collectors.summingInt(e -> 1))\n                );\n    }\n\n    public boolean notEmpty(Collection<?> collection) {\n        return !isEmpty(collection);\n    }\n\n    public boolean isEmpty(Collection<?> collection) {\n        return collection == null || collection.isEmpty();\n    }\n\n    public <T> Optional<T> findAny(Set<T> set) {\n        if (isEmpty(set)) {\n            return Optional.empty();\n        }\n\n        return set.stream().findAny();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/CompletableFutureKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.stream.Collectors;\n\n/**\n * CompletableFuture Kit\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\n@UtilityClass\npublic class CompletableFutureKit {\n    static final CompletableFuture<?>[] EMPTY_ARRAY = new CompletableFuture[0];\n\n    /**\n     * 并行调用多个 CompletableFuture 结果\n     *\n     * @param futures futures\n     * @param <T>     t\n     * @return 结果集\n     */\n    public <T> List<T> sequence(List<CompletableFuture<T>> futures) {\n        // 组合处理 allOf\n        CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futures.toArray(EMPTY_ARRAY));\n\n        return allDoneFuture.thenApply(v ->\n                futures.stream()\n                        .map(CompletableFuture::join)\n                        .filter(Objects::nonNull)\n                        .collect(Collectors.toList())\n        ).join();\n\n        // see https://nurkiewicz.com/2013/05/java-8-completablefuture-in-action.html\n    }\n\n    public <U> CompletableFuture<List<U>> sequenceAsync(List<CompletableFuture<U>> futures) {\n        // see https://nurkiewicz.com/2013/05/java-8-completablefuture-in-action.html\n\n        return CompletableFuture.allOf(futures.toArray(EMPTY_ARRAY)).thenApply(v -> {\n            // join all\n            return futures.stream()\n                    .map(CompletableFuture::join)\n                    .filter(Objects::nonNull)\n                    .collect(Collectors.toList());\n        });\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/ExecutorKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.NonNull;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * 线程执行器工具\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@UtilityClass\npublic class ExecutorKit {\n\n    /**\n     * 创建虚拟线程执行器\n     *\n     * @param name name\n     * @return 执行器\n     */\n    public ExecutorService newVirtualExecutor(String name) {\n        ThreadFactory factory = Thread.ofVirtual().name(name).factory();\n        return newVirtualExecutor(factory);\n    }\n\n    /**\n     * 创建虚拟线程执行器\n     *\n     * @param name  name\n     * @param start start\n     * @return 执行器\n     */\n    public ExecutorService newVirtualExecutor(String name, int start) {\n        ThreadFactory factory = Thread.ofVirtual().name(name, start).factory();\n        return newVirtualExecutor(factory);\n    }\n\n    /**\n     * 创建虚拟线程执行器\n     *\n     * @param factory factory\n     * @return 执行器\n     */\n    public ExecutorService newVirtualExecutor(ThreadFactory factory) {\n        return Executors.newThreadPerTaskExecutor(factory);\n    }\n\n    /**\n     * 创建单个线程执行器\n     *\n     * @param namePrefix 线程名\n     * @return 执行器\n     */\n    public ExecutorService newSingleThreadExecutor(String namePrefix) {\n        ThreadFactory threadFactory = createThreadFactory(namePrefix);\n        return newSingleThreadExecutor(threadFactory);\n    }\n\n    /**\n     * 创建单个线程执行器\n     *\n     * @param threadFactory 线程创建工厂\n     * @return 执行器\n     */\n    public ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {\n        return Executors.newSingleThreadExecutor(threadFactory);\n    }\n\n    /**\n     * 创建线程池\n     *\n     * @param namePrefix 线程名\n     * @return 执行器\n     */\n    public ExecutorService newCacheThreadPool(String namePrefix) {\n        ThreadFactory threadFactory = createThreadFactory(namePrefix);\n        return newCacheThreadPool(threadFactory);\n    }\n\n    /**\n     * 线程池\n     *\n     * @param threadFactory 线程创建工厂\n     * @return 执行器\n     */\n    public ExecutorService newCacheThreadPool(ThreadFactory threadFactory) {\n        return Executors.newCachedThreadPool(threadFactory);\n    }\n\n    /**\n     * 创建固定大小线程执行器\n     *\n     * @param corePoolSize  容量\n     * @param threadFactory 线程工厂\n     * @return 执行器\n     */\n    public ExecutorService newFixedThreadPool(int corePoolSize, ThreadFactory threadFactory) {\n        return Executors.newFixedThreadPool(corePoolSize, threadFactory);\n    }\n\n    /**\n     * 创建固定大小线程执行器\n     *\n     * @param corePoolSize 容量\n     * @param namePrefix   线程名\n     * @return 执行器\n     */\n    public ExecutorService newFixedThreadPool(int corePoolSize, String namePrefix) {\n        ThreadFactory threadFactory = createThreadFactory(namePrefix);\n        return newFixedThreadPool(corePoolSize, threadFactory);\n    }\n\n    /**\n     * 创建单个线程调度执行器\n     *\n     * @param threadFactory 线程创建工厂\n     * @return 调度 执行器\n     */\n    public ScheduledExecutorService newSingleScheduled(ThreadFactory threadFactory) {\n        return newScheduled(1, threadFactory);\n    }\n\n    /**\n     * 创建单个线程调度执行器\n     *\n     * @param namePrefix 线程名\n     * @return 调度 执行器\n     */\n    public ScheduledExecutorService newSingleScheduled(String namePrefix) {\n        ThreadFactory threadFactory = createThreadFactory(namePrefix);\n        return newScheduled(1, threadFactory);\n    }\n\n    /**\n     * 创建指定数量 - 的线程调度执行器\n     *\n     * @param corePoolSize 容量\n     * @param namePrefix   线程名\n     * @return 指定数量的 调度 执行器\n     */\n    public ScheduledExecutorService newScheduled(int corePoolSize, String namePrefix) {\n        ThreadFactory threadFactory = createThreadFactory(namePrefix);\n        return newScheduled(corePoolSize, threadFactory);\n    }\n\n    /**\n     * 创建指定数量 - 的线程调度执行器\n     *\n     * @param corePoolSize  容量\n     * @param threadFactory 线程创建工厂\n     * @return 指定数量的 调度 执行器\n     */\n    public ScheduledExecutorService newScheduled(int corePoolSize, ThreadFactory threadFactory) {\n        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);\n    }\n\n    /**\n     * 创建 线程工厂\n     * <pre>\n     *     daemon 参数默认是 true\n     * </pre>\n     *\n     * @param namePrefix 线程名\n     * @return 线程工厂\n     */\n    public ThreadFactory createThreadFactory(String namePrefix) {\n        return createThreadFactory(namePrefix, true);\n    }\n\n    /**\n     * 创建线程工厂\n     *\n     * @param namePrefix 线程名前缀\n     * @param daemon     置是否守护线程\n     * @return 线程工厂\n     */\n    public ThreadFactory createThreadFactory(@NonNull String namePrefix, boolean daemon) {\n        final AtomicLong threadNumber = new AtomicLong();\n\n        return runnable -> {\n\n            String threadName = namePrefix + threadNumber.getAndIncrement();\n\n            Thread thread = new Thread(runnable, threadName);\n\n            thread.setDaemon(daemon);\n\n            return thread;\n        };\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/HashKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * hash kit\n *\n * @author 渔民小镇\n * @date 2023-07-03\n */\n@UtilityClass\npublic class HashKit {\n    public int hash32(final String data) {\n        return MurmurHash3.hash32(data);\n    }\n\n    public int hash32(final byte[] data) {\n        return MurmurHash3.hash32(data);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/MoreKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-07\n */\n@UtilityClass\npublic class MoreKit {\n    public <T> T firstNonNull(T first, T second) {\n        if (first != null) {\n            return first;\n        }\n\n        if (second != null) {\n            return second;\n        }\n\n        throw new NullPointerException(\"两者为 null\");\n    }\n\n    public <K, T> T putIfAbsent(Map<K, T> map, K key, T value) {\n\n        var first = map.putIfAbsent(key, value);\n\n        return firstNonNull(first, value);\n    }\n\n    public void execute(Executor executor, Runnable runnable) {\n        if (executor == null) {\n            runnable.run();\n        } else {\n            executor.execute(runnable);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/MurmurHash3.java",
    "content": "/**\n * MurmurHash3 yields a 32-bit or 128-bit value.\n * <p>\n * MurmurHash is a non-cryptographic hash function suitable for general\n * hash-based lookup. The name comes from two basic operations, multiply (MU)\n * and rotate (R), used in its inner loop. Unlike cryptographic hash functions,\n * it is not specifically designed to be difficult to reverse by an adversary,\n * making it unsuitable for cryptographic purposes.\n * <p>\n * 32-bit Java port of\n * https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp#94\n * 128-bit Java port of\n * https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp#255\n * <p>\n * This is a public domain code with no copyrights. From homepage of MurmurHash\n * (https://code.google.com/p/smhasher/), \"All MurmurHash versions are public\n * domain software, and the author disclaims all copyright to their code.\"\n * <p>\n * Copied from Apache Hive:\n * https://github.com/apache/hive/blob/master/storage-api/src/java/org/apache/hive/common/util/Murmur3.java\n *\n * @see <a href=\"https://en.wikipedia.org/wiki/MurmurHash\">MurmurHash</a>\n * @since 1.13\n */\npackage com.iohao.game.common.kit;\n\n\nimport java.nio.charset.StandardCharsets;\n\n/**\n * copy from alibaba-broker\n * <pre>\n *     <a href=\"https://github.com/alibaba/alibaba-rsocket-broker\">...</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-28\n */\nfinal class MurmurHash3 {\n\n    static final int LONG_BYTES = Long.SIZE / Byte.SIZE;\n\n\n    static final int INTEGER_BYTES = Integer.SIZE / Byte.SIZE;\n\n\n    static final int SHORT_BYTES = Short.SIZE / Byte.SIZE;\n\n    private static final int C1_32 = 0xcc9e2d51;\n    private static final int C2_32 = 0x1b873593;\n    private static final int R1_32 = 15;\n    private static final int R2_32 = 13;\n    private static final int M_32 = 5;\n    private static final int N_32 = 0xe6546b64;\n\n    private static final long C1 = 0x87c37b91114253d5L;\n    private static final long C2 = 0x4cf5ad432745937fL;\n    private static final int R1 = 31;\n    private static final int R2 = 27;\n    private static final int R3 = 33;\n    private static final int M = 5;\n    private static final int N1 = 0x52dce729;\n    private static final int N2 = 0x38495ab5;\n\n    public static final int DEFAULT_SEED = 104729;\n\n    private MurmurHash3() {\n    }\n\n    /**\n     * Generates 32 bit hash from two longs with default seed value.\n     *\n     * @param l0 long to hash\n     * @param l1 long to hash\n     * @return 32 bit hash\n     */\n    public static int hash32(final long l0, final long l1) {\n        return hash32(l0, l1, DEFAULT_SEED);\n    }\n\n    /**\n     * Generates 32 bit hash from a long with default seed value.\n     *\n     * @param l0 long to hash\n     * @return 32 bit hash\n     */\n    public static int hash32(final long l0) {\n        return hash32(l0, DEFAULT_SEED);\n    }\n\n    /**\n     * Generates 32 bit hash from a long with the given seed.\n     *\n     * @param l0   long to hash\n     * @param seed initial seed value\n     * @return 32 bit hash\n     */\n    public static int hash32(final long l0, final int seed) {\n        int hash = seed;\n        final long r0 = Long.reverseBytes(l0);\n\n        hash = mix32((int) r0, hash);\n        hash = mix32((int) (r0 >>> 32), hash);\n\n        return fmix32(LONG_BYTES, hash);\n    }\n\n    /**\n     * Generates 32 bit hash from two longs with the given seed.\n     *\n     * @param l0   long to hash\n     * @param l1   long to hash\n     * @param seed initial seed value\n     * @return 32 bit hash\n     */\n    public static int hash32(final long l0, final long l1, final int seed) {\n        int hash = seed;\n        final long r0 = Long.reverseBytes(l0);\n        final long r1 = Long.reverseBytes(l1);\n\n        hash = mix32((int) r0, hash);\n        hash = mix32((int) (r0 >>> 32), hash);\n        hash = mix32((int) (r1), hash);\n        hash = mix32((int) (r1 >>> 32), hash);\n\n        return fmix32(LONG_BYTES * 2, hash);\n    }\n\n    /**\n     * Generates 32 bit hash from byte array with the default seed.\n     *\n     * @param data - input byte array\n     * @return 32 bit hash\n     */\n    public static int hash32(final byte[] data) {\n        return hash32(data, 0, data.length, DEFAULT_SEED);\n    }\n\n    /**\n     * Generates 32 bit hash from a string with the default seed.\n     *\n     * @param data - input string\n     * @return 32 bit hash\n     */\n    public static int hash32(final String data) {\n        final byte[] origin = data.getBytes(StandardCharsets.UTF_8);\n        return hash32(origin, 0, origin.length, DEFAULT_SEED);\n    }\n\n    /**\n     * Generates 32 bit hash from byte array with the default seed.\n     *\n     * @param data   - input byte array\n     * @param length - length of array\n     * @return 32 bit hash\n     */\n    public static int hash32(final byte[] data, final int length) {\n        return hash32(data, length, DEFAULT_SEED);\n    }\n\n    /**\n     * Generates 32 bit hash from byte array with the given length and seed.\n     *\n     * @param data   - input byte array\n     * @param length - length of array\n     * @param seed   - seed. (default 0)\n     * @return 32 bit hash\n     */\n    public static int hash32(final byte[] data, final int length, final int seed) {\n        return hash32(data, 0, length, seed);\n    }\n\n    /**\n     * Generates 32 bit hash from byte array with the given length, offset and seed.\n     *\n     * @param data   - input byte array\n     * @param offset - offset of data\n     * @param length - length of array\n     * @param seed   - seed. (default 0)\n     * @return 32 bit hash\n     */\n    public static int hash32(final byte[] data, final int offset, final int length, final int seed) {\n        int hash = seed;\n        final int nblocks = length >> 2;\n\n        // body\n        for (int i = 0; i < nblocks; i++) {\n            final int i_4 = i << 2;\n            final int k = (data[offset + i_4] & 0xff) | ((data[offset + i_4 + 1] & 0xff) << 8)\n                    | ((data[offset + i_4 + 2] & 0xff) << 16) | ((data[offset + i_4 + 3] & 0xff) << 24);\n\n            hash = mix32(k, hash);\n        }\n\n        // tail\n        final int idx = nblocks << 2;\n        int k1 = 0;\n        switch (length - idx) {\n            case 3:\n                k1 ^= data[offset + idx + 2] << 16;\n            case 2:\n                k1 ^= data[offset + idx + 1] << 8;\n            case 1:\n                k1 ^= data[offset + idx];\n\n                // mix functions\n                k1 *= C1_32;\n                k1 = Integer.rotateLeft(k1, R1_32);\n                k1 *= C2_32;\n                hash ^= k1;\n        }\n\n        return fmix32(length, hash);\n    }\n\n    /**\n     * Murmur3 64-bit variant. This is essentially MSB 8 bytes of Murmur3 128-bit\n     * variant.\n     *\n     * @param data - input byte array\n     * @return 64 bit hash\n     */\n    public static long hash64(final byte[] data) {\n        return hash64(data, 0, data.length, DEFAULT_SEED);\n    }\n\n    /**\n     * Murmur3 64-bit variant. This is essentially MSB 8 bytes of Murmur3 128-bit\n     * variant.\n     *\n     * @param data - input long\n     * @return 64 bit hash\n     */\n    public static long hash64(final long data) {\n        long hash = DEFAULT_SEED;\n        long k = Long.reverseBytes(data);\n        // mix functions\n        k *= C1;\n        k = Long.rotateLeft(k, R1);\n        k *= C2;\n        hash ^= k;\n        hash = Long.rotateLeft(hash, R2) * M + N1;\n        // finalization\n        hash ^= LONG_BYTES;\n        hash = fmix64(hash);\n        return hash;\n    }\n\n    /**\n     * Murmur3 64-bit variant. This is essentially MSB 8 bytes of Murmur3 128-bit\n     * variant.\n     *\n     * @param data - input int\n     * @return 64 bit hash\n     */\n    public static long hash64(final int data) {\n        long k1 = Integer.reverseBytes(data) & (-1L >>> 32);\n        long hash = DEFAULT_SEED;\n        k1 *= C1;\n        k1 = Long.rotateLeft(k1, R1);\n        k1 *= C2;\n        hash ^= k1;\n        // finalization\n        hash ^= INTEGER_BYTES;\n        hash = fmix64(hash);\n        return hash;\n    }\n\n    /**\n     * Murmur3 64-bit variant. This is essentially MSB 8 bytes of Murmur3 128-bit\n     * variant.\n     *\n     * @param data - input short\n     * @return 64 bit hash\n     */\n    public static long hash64(final short data) {\n        long hash = DEFAULT_SEED;\n        long k1 = 0;\n        k1 ^= ((long) data & 0xff) << 8;\n        k1 ^= ((long) ((data & 0xFF00) >> 8) & 0xff);\n        k1 *= C1;\n        k1 = Long.rotateLeft(k1, R1);\n        k1 *= C2;\n        hash ^= k1;\n\n        // finalization\n        hash ^= SHORT_BYTES;\n        hash = fmix64(hash);\n        return hash;\n    }\n\n    /**\n     * Generates 64 bit hash from byte array with the given length, offset and\n     * default seed.\n     *\n     * @param data   - input byte array\n     * @param offset - offset of data\n     * @param length - length of array\n     * @return 64 bit hash\n     */\n    public static long hash64(final byte[] data, final int offset, final int length) {\n        return hash64(data, offset, length, DEFAULT_SEED);\n    }\n\n    /**\n     * Generates 64 bit hash from byte array with the given length, offset and seed.\n     *\n     * @param data   - input byte array\n     * @param offset - offset of data\n     * @param length - length of array\n     * @param seed   - seed. (default 0)\n     * @return 64 bit hash\n     */\n    public static long hash64(final byte[] data, final int offset, final int length, final int seed) {\n        long hash = seed;\n        final int nblocks = length >> 3;\n\n        // body\n        for (int i = 0; i < nblocks; i++) {\n            final int i8 = i << 3;\n            long k = ((long) data[offset + i8] & 0xff) | (((long) data[offset + i8 + 1] & 0xff) << 8)\n                    | (((long) data[offset + i8 + 2] & 0xff) << 16) | (((long) data[offset + i8 + 3] & 0xff) << 24)\n                    | (((long) data[offset + i8 + 4] & 0xff) << 32) | (((long) data[offset + i8 + 5] & 0xff) << 40)\n                    | (((long) data[offset + i8 + 6] & 0xff) << 48) | (((long) data[offset + i8 + 7] & 0xff) << 56);\n\n            // mix functions\n            k *= C1;\n            k = Long.rotateLeft(k, R1);\n            k *= C2;\n            hash ^= k;\n            hash = Long.rotateLeft(hash, R2) * M + N1;\n        }\n\n        // tail\n        long k1 = 0;\n        final int tailStart = nblocks << 3;\n        switch (length - tailStart) {\n            case 7:\n                k1 ^= ((long) data[offset + tailStart + 6] & 0xff) << 48;\n            case 6:\n                k1 ^= ((long) data[offset + tailStart + 5] & 0xff) << 40;\n            case 5:\n                k1 ^= ((long) data[offset + tailStart + 4] & 0xff) << 32;\n            case 4:\n                k1 ^= ((long) data[offset + tailStart + 3] & 0xff) << 24;\n            case 3:\n                k1 ^= ((long) data[offset + tailStart + 2] & 0xff) << 16;\n            case 2:\n                k1 ^= ((long) data[offset + tailStart + 1] & 0xff) << 8;\n            case 1:\n                k1 ^= ((long) data[offset + tailStart] & 0xff);\n                k1 *= C1;\n                k1 = Long.rotateLeft(k1, R1);\n                k1 *= C2;\n                hash ^= k1;\n        }\n\n        // finalization\n        hash ^= length;\n        hash = fmix64(hash);\n\n        return hash;\n    }\n\n    /**\n     * Murmur3 128-bit variant.\n     *\n     * @param data - input byte array\n     * @return - 128 bit hash (2 longs)\n     */\n    public static long[] hash128(final byte[] data) {\n        return hash128(data, 0, data.length, DEFAULT_SEED);\n    }\n\n    /**\n     * Murmur3 128-bit variant.\n     *\n     * @param data - input String\n     * @return - 128 bit hash (2 longs)\n     */\n    public static long[] hash128(final String data) {\n        final byte[] origin = data.getBytes(StandardCharsets.UTF_8);\n        return hash128(origin, 0, origin.length, DEFAULT_SEED);\n    }\n\n    /**\n     * Murmur3 128-bit variant.\n     *\n     * @param data   - input byte array\n     * @param offset - the first element of array\n     * @param length - length of array\n     * @param seed   - seed. (default is 0)\n     * @return - 128 bit hash (2 longs)\n     */\n    public static long[] hash128(final byte[] data, final int offset, final int length, final int seed) {\n        long h1 = seed;\n        long h2 = seed;\n        final int nblocks = length >> 4;\n\n        // body\n        for (int i = 0; i < nblocks; i++) {\n            final int i16 = i << 4;\n            long k1 = ((long) data[offset + i16] & 0xff) | (((long) data[offset + i16 + 1] & 0xff) << 8)\n                    | (((long) data[offset + i16 + 2] & 0xff) << 16) | (((long) data[offset + i16 + 3] & 0xff) << 24)\n                    | (((long) data[offset + i16 + 4] & 0xff) << 32) | (((long) data[offset + i16 + 5] & 0xff) << 40)\n                    | (((long) data[offset + i16 + 6] & 0xff) << 48) | (((long) data[offset + i16 + 7] & 0xff) << 56);\n\n            long k2 = ((long) data[offset + i16 + 8] & 0xff) | (((long) data[offset + i16 + 9] & 0xff) << 8)\n                    | (((long) data[offset + i16 + 10] & 0xff) << 16) | (((long) data[offset + i16 + 11] & 0xff) << 24)\n                    | (((long) data[offset + i16 + 12] & 0xff) << 32) | (((long) data[offset + i16 + 13] & 0xff) << 40)\n                    | (((long) data[offset + i16 + 14] & 0xff) << 48) | (((long) data[offset + i16 + 15] & 0xff) << 56);\n\n            // mix functions for k1\n            k1 *= C1;\n            k1 = Long.rotateLeft(k1, R1);\n            k1 *= C2;\n            h1 ^= k1;\n            h1 = Long.rotateLeft(h1, R2);\n            h1 += h2;\n            h1 = h1 * M + N1;\n\n            // mix functions for k2\n            k2 *= C2;\n            k2 = Long.rotateLeft(k2, R3);\n            k2 *= C1;\n            h2 ^= k2;\n            h2 = Long.rotateLeft(h2, R1);\n            h2 += h1;\n            h2 = h2 * M + N2;\n        }\n\n        // tail\n        long k1 = 0;\n        long k2 = 0;\n        final int tailStart = nblocks << 4;\n        switch (length - tailStart) {\n            case 15:\n                k2 ^= (long) (data[offset + tailStart + 14] & 0xff) << 48;\n            case 14:\n                k2 ^= (long) (data[offset + tailStart + 13] & 0xff) << 40;\n            case 13:\n                k2 ^= (long) (data[offset + tailStart + 12] & 0xff) << 32;\n            case 12:\n                k2 ^= (long) (data[offset + tailStart + 11] & 0xff) << 24;\n            case 11:\n                k2 ^= (long) (data[offset + tailStart + 10] & 0xff) << 16;\n            case 10:\n                k2 ^= (long) (data[offset + tailStart + 9] & 0xff) << 8;\n            case 9:\n                k2 ^= data[offset + tailStart + 8] & 0xff;\n                k2 *= C2;\n                k2 = Long.rotateLeft(k2, R3);\n                k2 *= C1;\n                h2 ^= k2;\n\n            case 8:\n                k1 ^= (long) (data[offset + tailStart + 7] & 0xff) << 56;\n            case 7:\n                k1 ^= (long) (data[offset + tailStart + 6] & 0xff) << 48;\n            case 6:\n                k1 ^= (long) (data[offset + tailStart + 5] & 0xff) << 40;\n            case 5:\n                k1 ^= (long) (data[offset + tailStart + 4] & 0xff) << 32;\n            case 4:\n                k1 ^= (long) (data[offset + tailStart + 3] & 0xff) << 24;\n            case 3:\n                k1 ^= (long) (data[offset + tailStart + 2] & 0xff) << 16;\n            case 2:\n                k1 ^= (long) (data[offset + tailStart + 1] & 0xff) << 8;\n            case 1:\n                k1 ^= data[offset + tailStart] & 0xff;\n                k1 *= C1;\n                k1 = Long.rotateLeft(k1, R1);\n                k1 *= C2;\n                h1 ^= k1;\n        }\n\n        // finalization\n        h1 ^= length;\n        h2 ^= length;\n\n        h1 += h2;\n        h2 += h1;\n\n        h1 = fmix64(h1);\n        h2 = fmix64(h2);\n\n        h1 += h2;\n        h2 += h1;\n\n        return new long[]{h1, h2};\n    }\n\n    private static int mix32(int k, int hash) {\n        k *= C1_32;\n        k = Integer.rotateLeft(k, R1_32);\n        k *= C2_32;\n        hash ^= k;\n        return Integer.rotateLeft(hash, R2_32) * M_32 + N_32;\n    }\n\n    private static int fmix32(final int length, int hash) {\n        hash ^= length;\n        hash ^= (hash >>> 16);\n        hash *= 0x85ebca6b;\n        hash ^= (hash >>> 13);\n        hash *= 0xc2b2ae35;\n        hash ^= (hash >>> 16);\n\n        return hash;\n    }\n\n    private static long fmix64(long h) {\n        h ^= (h >>> 33);\n        h *= 0xff51afd7ed558ccdL;\n        h ^= (h >>> 33);\n        h *= 0xc4ceb9fe1a85ec53L;\n        h ^= (h >>> 33);\n        return h;\n    }\n\n    public static class IncrementalHash32 {\n        byte[] tail = new byte[3];\n        int tailLen;\n        int totalLen;\n        int hash;\n\n        public final void start(final int hash) {\n            tailLen = totalLen = 0;\n            this.hash = hash;\n        }\n\n        public final void add(final byte[] data, int offset, final int length) {\n            if (length == 0) {\n                return;\n            }\n            totalLen += length;\n            if (tailLen + length < 4) {\n                System.arraycopy(data, offset, tail, tailLen, length);\n                tailLen += length;\n                return;\n            }\n            int offset2 = 0;\n            if (tailLen > 0) {\n                offset2 = (4 - tailLen);\n                int k;\n                switch (tailLen) {\n                    case 1:\n                        k = orBytes(tail[0], data[offset], data[offset + 1], data[offset + 2]);\n                        break;\n                    case 2:\n                        k = orBytes(tail[0], tail[1], data[offset], data[offset + 1]);\n                        break;\n                    case 3:\n                        k = orBytes(tail[0], tail[1], tail[2], data[offset]);\n                        break;\n                    default:\n                        throw new AssertionError(tailLen);\n                }\n                // mix functions\n                k *= C1_32;\n                k = Integer.rotateLeft(k, R1_32);\n                k *= C2_32;\n                hash ^= k;\n                hash = Integer.rotateLeft(hash, R2_32) * M_32 + N_32;\n            }\n            final int length2 = length - offset2;\n            offset += offset2;\n            final int nblocks = length2 >> 2;\n\n            for (int i = 0; i < nblocks; i++) {\n                final int i_4 = (i << 2) + offset;\n                int k = orBytes(data[i_4], data[i_4 + 1], data[i_4 + 2], data[i_4 + 3]);\n\n                // mix functions\n                k *= C1_32;\n                k = Integer.rotateLeft(k, R1_32);\n                k *= C2_32;\n                hash ^= k;\n                hash = Integer.rotateLeft(hash, R2_32) * M_32 + N_32;\n            }\n\n            final int consumed = (nblocks << 2);\n            tailLen = length2 - consumed;\n            if (consumed == length2) {\n                return;\n            }\n            System.arraycopy(data, offset + consumed, tail, 0, tailLen);\n        }\n\n        public final int end() {\n            int k1 = 0;\n            switch (tailLen) {\n                case 3:\n                    k1 ^= tail[2] << 16;\n                case 2:\n                    k1 ^= tail[1] << 8;\n                case 1:\n                    k1 ^= tail[0];\n\n                    // mix functions\n                    k1 *= C1_32;\n                    k1 = Integer.rotateLeft(k1, R1_32);\n                    k1 *= C2_32;\n                    hash ^= k1;\n            }\n\n            // finalization\n            hash ^= totalLen;\n            hash ^= (hash >>> 16);\n            hash *= 0x85ebca6b;\n            hash ^= (hash >>> 13);\n            hash *= 0xc2b2ae35;\n            hash ^= (hash >>> 16);\n            return hash;\n        }\n    }\n\n    private static int orBytes(final byte b1, final byte b2, final byte b3, final byte b4) {\n        return (b1 & 0xff) | ((b2 & 0xff) << 8) | ((b3 & 0xff) << 16) | ((b4 & 0xff) << 24);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/NetworkKit.java",
    "content": "package com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * copy from\n * <pre>\n *     io.scalecube\n *     ali broker\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@UtilityClass\npublic class NetworkKit {\n    /**\n     * ip black list. 10.0.2.15 is default ip for virtual box vm\n     */\n    public final List<String> IP_BLACK_LIST = List.of(\"10.0.2.15\");\n    /** 类似 127.0.0.1 ，但这里是本机的 ip */\n    public String LOCAL_IP = getLocalIP();\n    public final Pattern ADDRESS_FORMAT = Pattern.compile(\"(?<host>^.*):(?<port>\\\\d+$)\");\n\n    public boolean hasPort(String hostAndPort, int port) {\n        int thatPort = getPort(hostAndPort);\n        return thatPort == port;\n    }\n\n    /**\n     * Parses given host:port string to create Address instance.\n     *\n     * @param hostAndPort must come in form {@code host:port}\n     */\n    public String getHost(String hostAndPort) {\n        if (hostAndPort == null || hostAndPort.isEmpty()) {\n            throw new IllegalArgumentException(\"host-and-port string must be present\");\n        }\n\n        Matcher matcher = ADDRESS_FORMAT.matcher(hostAndPort);\n        if (!matcher.find()) {\n            throw new IllegalArgumentException(\"can't parse host-and-port string from: \" + hostAndPort);\n        }\n\n        String host = matcher.group(1);\n        if (host == null || host.isEmpty()) {\n            throw new IllegalArgumentException(\"can't parse host from: \" + hostAndPort);\n        }\n\n        return host;\n    }\n\n    public int getPort(String hostAndPort) {\n        if (hostAndPort == null || hostAndPort.isEmpty()) {\n            throw new IllegalArgumentException(\"host-and-port string must be present\");\n        }\n\n        Matcher matcher = ADDRESS_FORMAT.matcher(hostAndPort);\n        if (!matcher.find()) {\n            throw new IllegalArgumentException(\"can't parse host-and-port string from: \" + hostAndPort);\n        }\n\n        int port;\n        try {\n            port = Integer.parseInt(matcher.group(2));\n        } catch (NumberFormatException ex) {\n            throw new IllegalArgumentException(\"can't parse port from: \" + hostAndPort, ex);\n        }\n\n        return port;\n    }\n\n    public boolean isInternalIp(String ipOrHost) {\n        try {\n            byte[] address = InetAddress.getByName(ipOrHost).getAddress();\n            return isInternalIp(address);\n        } catch (Exception e) {\n            System.out.println(\"Failed to get ip:\" + e.getMessage());\n            return false;\n        }\n    }\n\n    public boolean isInternalIp(byte[] addr) {\n        final byte b0 = addr[0];\n        final byte b1 = addr[1];\n        final byte b2 = addr[2];\n        final byte b3 = addr[3];\n        //10.x.x.x/8\n        final byte SECTION_1 = 0x0A;\n        //172.16.x.x/12\n        final byte SECTION_2 = (byte) 0xAC;\n        final byte SECTION_3 = (byte) 0x10;\n        final byte SECTION_4 = (byte) 0x1F;\n        //192.168.x.x/16\n        final byte SECTION_5 = (byte) 0xC0;\n        final byte SECTION_6 = (byte) 0xA8;\n        //127.0.0.1\n        final byte SECTION_7 = (byte) 127;\n        switch (b0) {\n            case SECTION_1:\n                return true;\n            case SECTION_2:\n                if (b1 >= SECTION_3 && b1 <= SECTION_4) {\n                    return true;\n                }\n            case SECTION_5:\n                if (b1 == SECTION_6) {\n                    return true;\n                }\n            case SECTION_7:\n                if (b1 == 0 && b2 == 0 && b3 == 1) {\n                    return true;\n                }\n            default:\n                return false;\n        }\n    }\n\n    private String getLocalIP() {\n        String ip = null;\n        try {\n            Enumeration<?> e = NetworkInterface.getNetworkInterfaces();\n            while (e.hasMoreElements()) {\n                NetworkInterface n = (NetworkInterface) e.nextElement();\n                Enumeration<?> ee = n.getInetAddresses();\n                while (ee.hasMoreElements()) {\n                    InetAddress inetAddress = (InetAddress) ee.nextElement();\n                    String hostAddress = inetAddress.getHostAddress();\n                    if (hostAddress.contains(\".\") && !IP_BLACK_LIST.contains(hostAddress) && !inetAddress.isLoopbackAddress()) {\n                        ip = hostAddress;\n                        break;\n                    }\n                }\n            }\n            if (ip == null) {\n                ip = InetAddress.getLocalHost().getHostAddress();\n            }\n        } catch (Exception ignore) {\n            return \"127.0.0.1\";\n        }\n        return ip;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/OperationCode.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\n/**\n * OperationCode\n *\n * @author 渔民小镇\n * @date 2025-01-08\n * @since 21.23\n */\npublic interface OperationCode {\n    int getOperationCode();\n\n    static int getAndIncrementCode() {\n        return OperationCodeKit.codeAtomic.getAndIncrement();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/PresentKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-01\n */\n@UtilityClass\npublic class PresentKit {\n\n    /**\n     * 如果属性为 null，则执行给定操作，否则不执行任何操作。\n     *\n     * @param obj      属性\n     * @param runnable 给定操作\n     */\n    public void ifNull(Object obj, Runnable runnable) {\n        if (Objects.isNull(obj)) {\n            runnable.run();\n        }\n    }\n\n    /**\n     * 如果属性不为 null，则执行给定操作，否则不执行任何操作。\n     *\n     * @param obj    属性\n     * @param action 给定操作\n     * @since 21.8\n     */\n    public <T> void ifPresent(T obj, Consumer<T> action) {\n        if (Objects.nonNull(obj)) {\n            action.accept(obj);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/RandomKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.random.RandomGenerator;\n\n/**\n * @author 渔民小镇\n * @date 2022-07-14\n */\n@UtilityClass\npublic class RandomKit {\n    final RandomGenerator generator = RandomGenerator.getDefault();\n\n    /**\n     * 获得指定范围内的随机数 [0,limit)\n     *\n     * @param limit 限制随机数的范围，不包括这个数\n     * @return 随机数\n     */\n    public int randomInt(int limit) {\n        return generator.nextInt(limit);\n    }\n\n    /**\n     * 获得指定范围内的随机数\n     *\n     * @param min 最小数（包含）\n     * @param max 最大数（不包含）\n     * @return 随机数\n     */\n    public int randomInt(int min, int max) {\n        return generator.nextInt(min, max);\n    }\n\n    /**\n     * 获得指定范围内的随机数 [0,limit)\n     *\n     * @param limit 限制随机数的范围，不包括这个数\n     * @return 随机数\n     * @since 21.23\n     */\n    public long randomLong(long limit) {\n        return generator.nextLong(limit);\n    }\n\n    /**\n     * 获得指定范围内的随机数\n     *\n     * @param min 最小数（包含）\n     * @param max 最大数（不包含）\n     * @return 随机数\n     * @since 21.23\n     */\n    public long randomLong(long min, long max) {\n        return generator.nextLong(min, max);\n    }\n\n    /**\n     * 获得指定范围内的随机数\n     *\n     * @param start 开始值（包含）\n     * @param end   结束值（包含）\n     * @return 随机数\n     */\n    public int random(int start, int end) {\n        return start + generator.nextInt(end - start + 1);\n    }\n\n    /**\n     * 获得指定范围内的随机数 (0 ~ end)\n     *\n     * @param end 结束值（包含）\n     * @return 随机数\n     */\n    public int random(int end) {\n        return generator.nextInt(end + 1);\n    }\n\n    /**\n     * 获得指定范围内的随机数\n     *\n     * @param start 开始值（包含）\n     * @param end   结束值（包含）\n     * @return 随机数\n     * @since 21.23\n     */\n    public long random(long start, long end) {\n        return start + generator.nextLong(end - start + 1);\n    }\n\n    /**\n     * 获得指定范围内的随机数 (0 ~ end)\n     *\n     * @param end 结束值（包含）\n     * @return 随机数\n     * @since 21.23\n     */\n    public long random(long end) {\n        return generator.nextLong(end + 1);\n    }\n\n    /**\n     * 随机一个 bool 值\n     *\n     * @return bool 值\n     */\n    public boolean randomBoolean() {\n        return generator.nextBoolean();\n    }\n\n    public <T> T randomEle(List<T> list) {\n\n        if (CollKit.isEmpty(list)) {\n            return null;\n        }\n\n        int size = list.size();\n\n        return size == 1\n                ? list.getFirst()\n                : list.get(randomInt(size));\n    }\n\n    public <T> T randomEle(T[] array) {\n        Objects.requireNonNull(array);\n\n        return array.length == 1\n                ? array[0]\n                : array[randomInt(array.length)];\n    }\n\n    public double nextDouble() {\n        return generator.nextDouble();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/RuntimeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * Runtime 相关工具\n *\n * @author 渔民小镇\n * @date 2024-05-01\n * @since 21.7\n */\n@UtilityClass\npublic class RuntimeKit {\n    /**\n     * 默认使用 Runtime.getRuntime().availableProcessors()。\n     * 如果有一些特殊环境需要模拟的，可以设置该变量。\n     */\n    public int availableProcessors = Runtime.getRuntime().availableProcessors();\n\n    /**\n     * 数量是不大于 Runtime.getRuntime().availableProcessors() 的 2 次幂。\n     * 当 availableProcessors 的值分别为 4、8、12、16、32 时，对应的数量则是 4、8、8、16、32。\n     * <p>\n     * for example\n     * <table>\n     *     <thead>\n     *         <th scope=\"col\">availableProcessors 值</th>\n     *         <th scope=\"col\">实际值</th>\n     *     </thead>\n     *     <tbody>\n     *         <tr><td>4</td><td>4</td></tr>\n     *         <tr><td>8</td><td>8</td></tr>\n     *         <tr><td>12</td><th scope=\"row\">8</th></tr>\n     *         <tr><td>16</td><td>16</td></tr>\n     *         <tr><td>32</td><td>32</td></tr>\n     *     </tbody>\n     * </table>\n     * <p>\n     * 另外，如果有一些特殊环境需要模拟的，可以设置该变量。\n     */\n    public int availableProcessors2n = availableProcessors2n();\n\n    static int availableProcessors2n() {\n        int n = RuntimeKit.availableProcessors;\n        n |= (n >> 1);\n        n |= (n >> 2);\n        n |= (n >> 4);\n        n |= (n >> 8);\n        n |= (n >> 16);\n        return (n + 1) >> 1;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/SafeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\n\n/**\n * 获取安全的值, 保证返回的不是 null\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@UtilityClass\npublic class SafeKit {\n    public int getInt(Integer value) {\n        return getInt(value, 0);\n    }\n\n    public int getInt(Integer value, int defaultValue) {\n        return value == null ? defaultValue : value;\n    }\n\n    public int getInt(String value, int defaultValue) {\n        try {\n            return Integer.parseInt(value);\n        } catch (NumberFormatException e) {\n            return defaultValue;\n        }\n    }\n\n    public long getLong(Long value) {\n        return getLong(value, 0);\n    }\n\n    public long getLong(Long value, long defaultValue) {\n        return Objects.isNull(value) ? defaultValue : value;\n    }\n\n    public long getLong(String value, long defaultValue) {\n        try {\n            return Long.parseLong(value);\n        } catch (NumberFormatException e) {\n            return defaultValue;\n        }\n    }\n\n    public boolean getBoolean(Boolean value) {\n        return getBoolean(value, false);\n    }\n\n    public boolean getBoolean(Boolean value, boolean defaultValue) {\n        return Objects.isNull(value) ? defaultValue : value;\n    }\n\n    public String getString(String value, String defaultValue) {\n        if (StrKit.isEmpty(value)) {\n            return defaultValue;\n        }\n\n        return value;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/StrKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport com.iohao.game.common.kit.adapter.AdapterHuUtils;\nimport lombok.NonNull;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-28\n */\n@UtilityClass\npublic class StrKit {\n\n    /**\n     * 首字母转换为大写\n     *\n     * @param value 字符串\n     * @return 转换后的字符串\n     */\n    public String firstCharToUpperCase(String value) {\n        char firstChar = value.charAt(0);\n        if (firstChar >= 'a' && firstChar <= 'z') {\n            char[] arr = value.toCharArray();\n            arr[0] -= 32;\n            return String.valueOf(arr);\n        }\n\n        return value;\n    }\n\n    public String firstCharToLowerCase(String value) {\n        char firstChar = value.charAt(0);\n        if (firstChar >= 'A' && firstChar <= 'Z') {\n            char[] arr = value.toCharArray();\n            arr[0] += 32;\n            return String.valueOf(arr);\n        }\n\n        return value;\n    }\n\n    public String format(@NonNull CharSequence template, @NonNull Map<?, ?> map) {\n        return AdapterHuUtils.format(template, map);\n    }\n\n    public String format(@NonNull CharSequence template, Object... params) {\n        return AdapterHuUtils.format(template, params);\n    }\n\n    /**\n     * <p>字符串是否为空，空的定义如下：</p>\n     * <ol>\n     *     <li>{@code null}</li>\n     *     <li>空字符串：{@code \"\"}</li>\n     * </ol>\n     *\n     * <p>例：</p>\n     * <ul>\n     *     <li>{@code StrKit.isEmpty(null)     // true}</li>\n     *     <li>{@code StrKit.isEmpty(\"\")       // true}</li>\n     *     <li>{@code StrKit.isEmpty(\" \\t\\n\")  // false}</li>\n     *     <li>{@code StrKit.isEmpty(\"abc\")    // false}</li>\n     * </ul>\n     *\n     * <p>建议：</p>\n     * <ul>\n     *     <li>该方法建议用于工具类或任何可以预期的方法参数的校验中。</li>\n     * </ul>\n     *\n     * @param str 被检测的字符串\n     * @return 是否为空\n     */\n    public boolean isEmpty(String str) {\n        return str == null || str.isBlank();\n    }\n\n    public boolean isEmpty(CharSequence str) {\n        return str == null || str.isEmpty();\n    }\n\n    /**\n     * <p>字符串是否为非空白，非空白的定义如下： </p>\n     * <ol>\n     *     <li>不为 {@code null}</li>\n     *     <li>不为空字符串：{@code \"\"}</li>\n     * </ol>\n     *\n     * <p>例：</p>\n     * <ul>\n     *     <li>{@code StrKit.isNotEmpty(null)     // false}</li>\n     *     <li>{@code StrKit.isNotEmpty(\"\")       // false}</li>\n     *     <li>{@code StrKit.isNotEmpty(\" \\t\\n\")  // true}</li>\n     *     <li>{@code StrKit.isNotEmpty(\"abc\")    // true}</li>\n     * </ul>\n     *\n     * <p>建议：该方法建议用于工具类或任何可以预期的方法参数的校验中。</p>\n     *\n     * @param str 被检测的字符串\n     * @return 是否为非空\n     */\n    public boolean isNotEmpty(String str) {\n        return !isEmpty(str);\n    }\n\n    public boolean isBlank(CharSequence cs) {\n        int strLen;\n        if (cs == null || (strLen = cs.length()) == 0) {\n            return true;\n        }\n\n        for (int i = 0; i < strLen; i++) {\n            if (!Character.isWhitespace(cs.charAt(i))) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    public boolean isNotBlank(CharSequence cs) {\n        return !isBlank(cs);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/TimeBetweenKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport java.io.Serializable;\nimport java.time.LocalTime;\n\n/**\n * 时间段范围\n * <pre>\n *     23:00~1:30\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-20\n * @deprecated 暂无代替，如有需要请 copy 现有的相关代码\n */\n@Deprecated\npublic class TimeBetweenKit implements Serializable {\n    /**\n     * 一天的分钟总数\n     */\n    static final int oneDayMinute = 24 * 60;\n\n    /**\n     * 指定时间是否在 时间段范围内\n     * <pre>\n     * 示例:\n     *     起始时间 23:00, 结束时间 1:30\n     *\n     *     当前时间在起始时间与结束时间之内就是时间段内,\n     *     如果当前时间是 1:31, 就不属于时间段内\n     * </pre>\n     *\n     * @param startTime 起始时间 格式 [小时:分钟] . 如: 23:00, 1:30\n     * @param endTime   结束时间 格式 [小时:分钟] . 如: 23:00, 1:30\n     * @param nowTime   当前时间是否在 起始时间与结束时间之内\n     * @return true 起始时间与结束时间之内\n     */\n    public static boolean betweenNowTime(String startTime, String endTime, LocalTime nowTime) {\n\n        int startTimeIndex = convertTimeIndex(startTime);\n        int endTimeIndex = convertTimeIndex(endTime);\n\n        return betweenNowTime(startTimeIndex, endTimeIndex, nowTime);\n    }\n\n    /**\n     * 指定时间是否在 时间段范围内\n     * <pre>\n     * 示例:\n     *     起始时间 23:00, 结束时间 1:30\n     *\n     *     当前时间在起始时间与结束时间之内就是时间段内,\n     *     如果当前时间是 1:31, 就不属于时间段内\n     * </pre>\n     *\n     * @param startTimeIndex 起始时间数值 把格式 [小时:分钟] . 如: 23:00, 1:30的时间格式转换成 时间数值\n     * @param endTimeIndex   结束时间数值 格式 [小时:分钟] . 如: 23:00, 1:30的时间格式转换成 时间数值\n     * @param nowTime        当前时间是否在 起始时间与结束时间之内\n     * @return true 起始时间与结束时间之内\n     */\n    public static boolean betweenNowTime(int startTimeIndex, int endTimeIndex, LocalTime nowTime) {\n        if (startTimeIndex == endTimeIndex) {\n            return false;\n        }\n\n        int nowTimeIndex = convertTimeIndex(nowTime);\n\n        boolean success;\n        // 跨天\n        if (startTimeIndex > endTimeIndex) {\n            if (nowTimeIndex >= startTimeIndex) {\n                // 说明是当天的时间段比较\n                success = true;\n            } else {\n                /*\n                 * 跨天时间段:\n                 * 1 给当前时间加上全天分钟数\n                 * 2 给结束时间加上全天分钟数\n                 */\n                int upNowTimeIndex = nowTimeIndex + oneDayMinute;\n                int upEndIndex = endTimeIndex + oneDayMinute;\n                success = upNowTimeIndex >= startTimeIndex && upNowTimeIndex <= upEndIndex;\n            }\n        } else {\n            success = nowTimeIndex >= startTimeIndex && nowTimeIndex <= endTimeIndex;\n        }\n\n        return success;\n    }\n\n    /**\n     * 当前时间是否在 时间段范围内\n     * <pre>\n     * 示例:\n     *     起始时间 23:00, 结束时间 1:30\n     *\n     *     当前时间在起始时间与结束时间之内就是时间段内,\n     *     如果当前时间是 1:31, 就不属于时间段内\n     * </pre>\n     *\n     * @param startTime 起始时间 格式 [小时:分钟] . 如: 23:00, 1:30\n     * @param endTime   结束时间 格式 [小时:分钟] . 如: 23:00, 1:30\n     * @return true 起始时间与结束时间之内\n     */\n    public static boolean betweenNowTime(String startTime, String endTime) {\n        return betweenNowTime(startTime, endTime, LocalTime.now());\n    }\n\n    /**\n     * 将 LocalTime 的时间和分钟转换成 时间数值\n     *\n     * @param localTime LocalTime\n     * @return 转换后的 时间数值\n     */\n    private static int convertTimeIndex(LocalTime localTime) {\n        int hour = localTime.getHour();\n        int minute = localTime.getMinute();\n        return convertTimeIndex(hour, minute);\n    }\n\n    /**\n     * 将小时和分钟转换成 时间数值\n     *\n     * @param time 小时和分钟. 格式 [小时:分钟] . 如: 23:00, 1:30\n     * @return 转换后的 时间数值\n     */\n    public static int convertTimeIndex(String time) {\n        String[] split = time.split(\":\");\n        int hours = Integer.parseInt(split[0]);\n        int minute = Integer.parseInt(split[1]);\n        return convertTimeIndex(hours, minute);\n    }\n\n    /**\n     * 将小时和分钟转换成 时间数值\n     *\n     * @param hours  小时\n     * @param minute 分钟\n     * @return 转换后的 时间数值\n     */\n    private static int convertTimeIndex(int hours, int minute) {\n        return hours * 60 + minute;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/TimeFormatterKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport com.iohao.game.common.kit.time.ConfigTimeKit;\nimport com.iohao.game.common.kit.time.FormatTimeKit;\nimport lombok.experimental.UtilityClass;\n\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\n\n/**\n * 时间格式化相关工具\n *\n * @author 渔民小镇\n * @date 2024-07-06\n * @since 21.11\n * @deprecated 请使用 {@link com.iohao.game.common.kit.time.FormatTimeKit}\n */\n@Deprecated\n@UtilityClass\npublic class TimeFormatterKit {\n    /**\n     * @deprecated 请使用 {@link ConfigTimeKit#getDefaultZoneId()}\n     */\n    public ZoneId defaultZoneId = ConfigTimeKit.getDefaultZoneId();\n    @Deprecated\n    public DateTimeFormatter defaultFormatter = FormatTimeKit.ofPattern(\"yyyy-MM-dd HH:mm:ss\");\n\n    @Deprecated\n    public DateTimeFormatter ofPattern(String pattern) {\n        return FormatTimeKit.ofPattern(pattern);\n    }\n\n    @Deprecated\n    public String formatter() {\n        return defaultFormatter.format(LocalDateTime.now());\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/TimeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit;\n\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.common.kit.time.*;\n\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\nimport java.time.*;\nimport java.time.format.DateTimeFormatter;\nimport java.time.temporal.TemporalAccessor;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 时间工具\n *\n * @author 渔民小镇\n * @date 2023-07-12\n * @deprecated 请使用 {@link com.iohao.game.common.kit.time} 相关类\n */\n@Deprecated\n@UtilityClass\npublic class TimeKit {\n    /**\n     * @deprecated 请使用 {@link ConfigTimeKit#getDefaultZoneId()}\n     */\n    @Deprecated\n    public ZoneId defaultZoneId = ConfigTimeKit.getDefaultZoneId();\n    /**\n     * @deprecated 请使用 {@link FormatTimeKit#ofPattern(String)}\n     */\n    @Deprecated\n    public DateTimeFormatter defaultFormatter = FormatTimeKit.ofPattern(\"yyyy-MM-dd HH:mm:ss\");\n    /**\n     * @deprecated 请使用 {@link FormatTimeKit#ofPattern(String)}\n     */\n    @Deprecated\n    public final DateTimeFormatter dateFormatterYMD = FormatTimeKit.ofPattern(\"yyyy-MM-dd\");\n    @Deprecated\n    final DateTimeFormatter dateFormatterYMDShort = FormatTimeKit.ofPattern(\"yyyyMMdd\");\n\n    volatile LocalDate localDate;\n    volatile long currentTimeMillis;\n\n    /** 时间更新策略 */\n    @Setter\n    @Deprecated\n    UpdateCurrentTimeMillis updateCurrentTimeMillis;\n\n    /**\n     * 获取 LocalDate，默认每分钟更新一次，可有效减少 LocalDate 对象的创建。\n     *\n     * @return LocalDate\n     * @deprecated 请使用 {@link CacheTimeKit#nowLocalDate()}\n     */\n    @Deprecated\n    public LocalDate nowLocalDate() {\n\n        if (Objects.nonNull(localDate)) {\n            return localDate;\n        }\n\n        if (Objects.isNull(localDate)) {\n            LocalDateTimeUpdatingStrategy.me().update();\n        }\n\n        return LocalDate.now();\n    }\n\n    /**\n     * 获取 currentTimeMillis 的时间，默认每秒更新一次，如果对时间要求不需要很精准的，可以考虑使用。\n     *\n     * @return System.currentTimeMillis()\n     * @deprecated 请使用 {@link CacheTimeKit#currentTimeMillis()} 代替\n     */\n    @Deprecated\n    public long currentTimeMillis() {\n\n        if (currentTimeMillis != 0) {\n            return currentTimeMillis;\n        }\n\n        if (Objects.nonNull(updateCurrentTimeMillis)) {\n            return updateCurrentTimeMillis.getCurrentTimeMillis();\n        }\n\n        if (currentTimeMillis == 0) {\n            SecondUpdatingStrategy.me().update();\n        }\n\n        return System.currentTimeMillis();\n    }\n\n    /**\n     * toSecond\n     *\n     * @param localDateTime localDateTime\n     * @return toSecond\n     * @deprecated {@link ToTimeKit#toSeconds(LocalDateTime)}\n     */\n    @Deprecated\n    public int toSecond(LocalDateTime localDateTime) {\n        // 获取毫秒数\n        return ToTimeKit.toSeconds(localDateTime);\n    }\n\n    /**\n     * toMilli\n     *\n     * @param localDateTime localDateTime\n     * @return timeMillis\n     * @deprecated 请使用 {@link ToTimeKit#toMillis(LocalDateTime)}\n     */\n    @Deprecated\n    public long toMilli(LocalDateTime localDateTime) {\n        // 获取毫秒数\n        return ToTimeKit.toMillis(localDateTime);\n    }\n\n    /**\n     * toMilli\n     *\n     * @param localDate localDate\n     * @return toMilli\n     * @deprecated 请使用 {@link ToTimeKit#toMillis(LocalDateTime)}\n     */\n    @Deprecated\n    public long toMilli(LocalDate localDate) {\n        var localDateTime = LocalDateTime.of(localDate, LocalTime.MIDNIGHT);\n        // 转换为毫秒\n        return ToTimeKit.toMillis(localDateTime);\n    }\n\n    /**\n     * toInstant\n     *\n     * @param localDateTime localDateTime\n     * @return Instant\n     * @deprecated 请使用 {@link ToTimeKit#toInstant(LocalDateTime)}\n     */\n    @Deprecated\n    public Instant toInstant(LocalDateTime localDateTime) {\n        return localDateTime\n                .atZone(defaultZoneId)\n                .toInstant();\n    }\n\n    /**\n     * toLocalDateTime\n     *\n     * @param milliseconds milliseconds\n     * @return LocalDateTime\n     * @deprecated {@link ToTimeKit#toLocalDateTime(long)}\n     */\n    @Deprecated\n    public LocalDateTime toLocalDateTime(long milliseconds) {\n        return ToTimeKit.toLocalDateTime(milliseconds);\n    }\n\n    /**\n     * formatter\n     *\n     * @param localDateTime localDateTime\n     * @return fmt\n     * @deprecated 请使用 {@link FormatTimeKit#format(TemporalAccessor)}\n     */\n    @Deprecated\n    public String formatter(LocalDateTime localDateTime) {\n        return FormatTimeKit.format(localDateTime);\n    }\n\n    /**\n     * formatter\n     *\n     * @return fmt\n     * @deprecated 请使用 {@link FormatTimeKit#format()}\n     */\n    @Deprecated\n    public String formatter() {\n        return formatter(currentTimeMillis());\n    }\n\n    /**\n     * formatter\n     *\n     * @param milliseconds milliseconds\n     * @return String\n     * @deprecated {@link FormatTimeKit#format(long)}\n     */\n    @Deprecated\n    public String formatter(long milliseconds) {\n        return FormatTimeKit.format(milliseconds);\n    }\n\n    /**\n     * 将 LocalDate 转为 number\n     *\n     * @param localDate localDate\n     * @return long\n     */\n    @Deprecated\n    public long localDateToNumber(LocalDate localDate) {\n        return toMilli(localDate);\n    }\n\n    /**\n     * 过期检测\n     *\n     * @param epochDay 格式 yyyyMMdd\n     * @return true 表示日期已经过期\n     * @deprecated {@link ExpireTimeKit#expireLocalDate(long)}\n     */\n    @Deprecated\n    public boolean expireLocalDate(long epochDay) {\n        return ExpireTimeKit.expireLocalDate(epochDay);\n    }\n\n    /**\n     * 过期检测\n     * <pre>\n     *     与当前时间做比较，查看是否过期\n     * </pre>\n     *\n     * @param milliseconds 需要检测的时间\n     * @return true milliseconds 已经过期\n     * @deprecated {@link ExpireTimeKit#expireMillis(long)}\n     */\n    @Deprecated\n    public boolean expire(long milliseconds) {\n        // 时间 - 当前时间\n        return ExpireTimeKit.expireMillis(milliseconds);\n    }\n\n    @Deprecated\n    public interface UpdateCurrentTimeMillis {\n        default long getCurrentTimeMillis() {\n            return System.currentTimeMillis();\n        }\n    }\n\n    @Deprecated\n    interface TimeUpdatingStrategy {\n        default void update() {\n        }\n    }\n\n    @Deprecated\n    private final class LocalDateTimeUpdatingStrategy implements TimeUpdatingStrategy {\n\n        private LocalDateTimeUpdatingStrategy() {\n            localDate = LocalDate.now();\n\n            TaskKit.runInterval(() -> {\n                // 每分钟更新一次当前时间\n                localDate = LocalDate.now();\n            }, 1, TimeUnit.MINUTES);\n        }\n\n        public static LocalDateTimeUpdatingStrategy me() {\n            return Holder.ME;\n        }\n\n        /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n        private static class Holder {\n            static final LocalDateTimeUpdatingStrategy ME = new LocalDateTimeUpdatingStrategy();\n        }\n    }\n\n    @Deprecated\n    private final class SecondUpdatingStrategy implements TimeUpdatingStrategy {\n        private SecondUpdatingStrategy() {\n            currentTimeMillis = System.currentTimeMillis();\n\n            TaskKit.runInterval(() -> {\n                // 每秒更新一次当前时间\n                currentTimeMillis = System.currentTimeMillis();\n            }, 1, TimeUnit.SECONDS);\n        }\n\n        public static SecondUpdatingStrategy me() {\n            return Holder.ME;\n        }\n\n        /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n        private static class Holder {\n            static final SecondUpdatingStrategy ME = new SecondUpdatingStrategy();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/attr/AttrOption.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.attr;\n\nimport java.io.Serializable;\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * 动态属性的属性项\n * <pre>\n *     see {@link AttrOptionDynamic}\n * </pre>\n *\n * @param <T> t\n * @author 渔民小镇\n * @date 2022-01-31\n */\npublic final class AttrOption<T> implements Serializable {\n    final String name;\n    final T defaultValue;\n    final Supplier<T> supplier;\n\n    AttrOption(String name, T devault, Supplier<T> supplier) {\n        Objects.requireNonNull(name);\n\n        this.name = name;\n        this.defaultValue = devault;\n        this.supplier = supplier;\n    }\n\n    public String name() {\n        return this.name;\n    }\n\n    public T defaultValue() {\n        return this.defaultValue;\n    }\n\n    /**\n     * 初始化 一个 AttrOption\n     *\n     * @param name name\n     * @param <T>  t\n     * @return AttrOption\n     */\n    public static <T> AttrOption<T> valueOf(String name) {\n        return new AttrOption<>(name, null, null);\n    }\n\n    /**\n     * 初始化 一个 AttrOption\n     *\n     * @param name         name\n     * @param defaultValue 默认值（单例）\n     * @param <T>          t\n     * @return AttrOption\n     */\n    public static <T> AttrOption<T> valueOf(String name, T defaultValue) {\n        return new AttrOption<>(name, defaultValue, null);\n    }\n\n    /**\n     * 初始化一个 AttrOption\n     *\n     * @param name     name\n     * @param supplier 如果值不存在，则从 supplier 获取\n     * @param <T>      t\n     * @return AttrOption\n     */\n    public static <T> AttrOption<T> valueOf(String name, Supplier<T> supplier) {\n        return new AttrOption<>(name, null, supplier);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n\n        AttrOption<?> that = (AttrOption<?>) o;\n\n        return Objects.equals(name, that.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return this.name.hashCode();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/attr/AttrOptionDynamic.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.attr;\n\nimport java.util.Objects;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * 动态属性 (类型明确的)\n * <pre>\n *     实现该接口的对象, 都会提供动态属性机制\n *     避免类型转换\n * </pre>\n * AttrOptionDynamic options = ...;\n * <pre>\n *     使用示例 - 获取属性 :\n *     AttrOption&lt;Long&gt; timeKey = AttrOption.valueOf(\"myLongValue\");\n *     // set long value\n *     this.option(timeKey, 123L);\n *     // get long value\n *     long val = this.option(timeKey);\n *\n *     AttrOption&lt;Integer&gt; intKey = AttrOption.valueOf(\"myIntegerValue\");\n *     // set int value\n *     this.option(intKey, 123);\n *     // get int value\n *     int age = this.option(intKey);\n *\n * </pre>\n * <pre>\n *     如果你使用了lombok, 推荐这种方式. 只需要在对象中新增此行代码\n *     final AttrOptions options = new AttrOptions();\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-31\n */\npublic interface AttrOptionDynamic {\n    /**\n     * 获取动态成员属性\n     *\n     * @return 动态成员属性\n     */\n    AttrOptions getOptions();\n\n    /**\n     * 获取选项值，如果选项不存在，返回默认值。\n     *\n     * @param option 选项值\n     * @return 如果 option 不存在，则使用默认的 option 值。\n     */\n    default <T> T option(AttrOption<T> option) {\n        return this.getOptions().option(option);\n    }\n\n    /**\n     * 获取选项值，如果选项不存在则返回设定值。\n     *\n     * @param option 选项值\n     * @param value  设定值\n     * @return 如果 option 不存在，则使用设定值。\n     */\n    default <T> T optionValue(AttrOption<T> option, T value) {\n        T data = this.option(option);\n\n        if (Objects.isNull(data)) {\n            return value;\n        }\n\n        return data;\n    }\n\n    /**\n     * 设置一个具有特定值的新选项。\n     * <p>\n     * 使用 null 值删除前一个设置的 {@link AttrOption}。\n     *\n     * @param option 选项值\n     * @param value  选项值, null 用于删除前一个 {@link AttrOption}.\n     * @return this\n     */\n    default <T> AttrOptions option(AttrOption<T> option, T value) {\n        return this.getOptions().option(option, value);\n    }\n\n    /**\n     * 如果动态属性存在，则执行给定的操作，否则不执行任何操作。\n     *\n     * @param option   option\n     * @param consumer 给定的操作。只有 option 的值存在且不为 null 时，才会执行的操作。\n     * @param <T>      t\n     */\n    default <T> void ifPresent(AttrOption<T> option, Consumer<T> consumer) {\n        T data = this.option(option);\n        if (Objects.nonNull(data)) {\n            consumer.accept(data);\n        }\n    }\n\n    /**\n     * 如果动态属性值为 null，则执行给定的操作，否则不执行任何操作。执行给定操作后将得到一个返回值，该返回值会设置到动态属性中。\n     *\n     * @param option   option\n     * @param supplier 给定的操作。当 option 的值为 null 时，才会执行的操作\n     * @param <T>      t\n     */\n    default <T> void ifNull(AttrOption<T> option, Supplier<T> supplier) {\n        T data = this.option(option);\n        if (Objects.isNull(data)) {\n            this.option(option, supplier.get());\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/attr/AttrOptions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.attr;\n\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 动态属性的选项载体\n * <pre>\n *     see {@link AttrOptionDynamic}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-31\n */\npublic class AttrOptions implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 9042891580724596100L;\n\n    final Map<AttrOption<?>, Object> options = new NonBlockingHashMap<>();\n\n    /**\n     * 获取选项值。\n     * <p>\n     * 如果选项不存在，返回默认值。\n     *\n     * @param option 选项值\n     * @return 如果 option 不存在，则使用默认的 option 值。\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> T option(AttrOption<T> option) {\n        Object value = options.get(option);\n        if (Objects.nonNull(value)) {\n            return (T) value;\n        }\n\n        if (Objects.nonNull(option.supplier)) {\n            T newValue = option.supplier.get();\n            this.option(option, newValue);\n            return newValue;\n        }\n\n        if (Objects.nonNull(option.defaultValue)) {\n            return option.defaultValue;\n        }\n\n        return null;\n    }\n\n    /**\n     * 设置一个具有特定值的新选项。\n     * <p>\n     * 使用 null 值删除前一个设置的 {@link AttrOption}。\n     *\n     * @param option 选项值\n     * @param value  选项值, null 用于删除前一个 {@link AttrOption}.\n     * @return this\n     */\n    public <T> AttrOptions option(AttrOption<T> option, T value) {\n        if (Objects.isNull(value)) {\n            options.remove(option);\n            return this;\n        }\n\n        options.put(option, value);\n        return this;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/attr/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - 动态属性\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\npackage com.iohao.game.common.kit.attr;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/AbstractPropertyValueObservable.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\nimport java.util.Objects;\n\n/**\n * PropertyValueObservable adapter\n *\n * @author 渔民小镇\n * @date 2024-04-17\n * @see IntegerProperty\n * @see LongProperty\n * @see StringProperty\n * @see BooleanProperty\n * @see ObjectProperty\n */\nabstract class AbstractPropertyValueObservable<T> implements PropertyValueObservable<T> {\n    protected boolean valid = true;\n    ChangeHelperList<T> helperList;\n\n    @Override\n    public void addListener(PropertyChangeListener<? super T> listener) {\n        if (Objects.isNull(this.helperList)) {\n            this.helperList = new ChangeHelperList<>();\n        }\n\n        this.helperList.addListener(this, listener);\n    }\n\n    @Override\n    public void removeListener(PropertyChangeListener<? super T> listener) {\n        if (Objects.nonNull(this.helperList)) {\n            this.helperList.removeListener(listener);\n        }\n    }\n\n    protected void markInvalid() {\n        if (this.valid) {\n            this.valid = false;\n            this.fireValueChangedEvent();\n        }\n    }\n\n    private void fireValueChangedEvent() {\n        if (Objects.nonNull(this.helperList)) {\n            this.helperList.fireValueChangedEvent();\n        }\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/BooleanProperty.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\nimport lombok.ToString;\n\nimport java.util.Objects;\n\n/**\n * bool - 属性具备监听特性。当值发生变更时，会触发监听事件。\n * <pre>{@code\n *         var property = new BooleanProperty();\n *         // add listener monitor property object\n *         property.addListener((observable, oldValue, newValue) -> {\n *             log.info(\"oldValue:{}, newValue:{}\", oldValue, newValue);\n *         });\n *\n *         property.get(); // value is false\n *         property.set(true); // When the value changes,listeners are triggered\n *         property.get(); // value is true\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\n@ToString\npublic final class BooleanProperty extends AbstractPropertyValueObservable<Boolean> {\n    boolean value;\n\n    public BooleanProperty() {\n        this(false);\n    }\n\n    public BooleanProperty(boolean value) {\n        this.value = value;\n    }\n\n    @Override\n    public Boolean getValue() {\n        return get();\n    }\n\n    @Override\n    public void setValue(Boolean value) {\n        this.set(Objects.requireNonNullElse(value, false));\n    }\n\n    /**\n     * get current value\n     *\n     * @return current value\n     */\n    public boolean get() {\n        this.valid = true;\n        return this.value;\n    }\n\n    /**\n     * set current value\n     *\n     * @param newValue current new value\n     */\n    public void set(boolean newValue) {\n        if (newValue != this.value) {\n            this.value = newValue;\n            markInvalid();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/IntegerProperty.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\nimport lombok.ToString;\n\n/**\n * int - 属性具备监听特性。当值发生变更时，会触发监听事件。\n * <pre>{@code\n *         var property = new IntegerProperty();\n *         // add listener monitor property object\n *         property.addListener((observable, oldValue, newValue) -> {\n *             log.info(\"oldValue:{}, newValue:{}\", oldValue, newValue);\n *         });\n *\n *         property.get(); // value is 0\n *         property.set(22); // When the value changes,listeners are triggered\n *         property.get(); // value is 22\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\n@ToString\npublic final class IntegerProperty extends NumberPropertyValueObservable {\n    int value;\n\n    public IntegerProperty() {\n        this(0);\n    }\n\n    public IntegerProperty(int value) {\n        this.value = value;\n    }\n\n    @Override\n    public Integer getValue() {\n        return get();\n    }\n\n    @Override\n    public void setValue(Number value) {\n        if (value == null) {\n            this.set(0);\n        } else {\n            this.set(value.intValue());\n        }\n    }\n\n    /**\n     * get current value\n     *\n     * @return current value\n     */\n    public int get() {\n        this.valid = true;\n        return this.value;\n    }\n\n    /**\n     * set current value\n     *\n     * @param newValue current new value\n     */\n    public void set(int newValue) {\n        if (newValue != this.value) {\n            this.value = newValue;\n            markInvalid();\n        }\n    }\n\n    /**\n     * current value + 1\n     */\n    public void increment() {\n        this.set(this.value + 1);\n    }\n\n    /**\n     * current value - 1\n     */\n    public void decrement() {\n        this.set(this.value - 1);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/LongProperty.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\nimport lombok.ToString;\n\n/**\n * long - 属性具备监听特性。当值发生变更时，会触发监听事件。\n * <pre>{@code\n *         var property = new LongProperty();\n *         // add listener monitor property object\n *         property.addListener((observable, oldValue, newValue) -> {\n *             log.info(\"oldValue:{}, newValue:{}\", oldValue, newValue);\n *         });\n *\n *         property.get(); // value is 0\n *         property.set(22); // When the value changes,listeners are triggered\n *         property.get(); // value is 22\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\n@ToString\npublic final class LongProperty extends NumberPropertyValueObservable {\n    long value;\n\n    public LongProperty() {\n        this(0);\n    }\n\n    public LongProperty(long value) {\n        this.value = value;\n    }\n\n    @Override\n    public Long getValue() {\n        return get();\n    }\n\n    @Override\n    public void setValue(Number value) {\n        if (value == null) {\n            this.set(0);\n        } else {\n            this.set(value.longValue());\n        }\n    }\n\n    /**\n     * get current value\n     *\n     * @return current value\n     */\n    public long get() {\n        this.valid = true;\n        return this.value;\n    }\n\n    /**\n     * set current value\n     *\n     * @param newValue current new value\n     */\n    public void set(long newValue) {\n        if (newValue != this.value) {\n            this.value = newValue;\n            markInvalid();\n        }\n    }\n\n    /**\n     * current value + 1\n     */\n    public void increment() {\n        this.set(this.value + 1);\n    }\n\n    /**\n     * current value - 1\n     */\n    public void decrement() {\n        this.set(this.value - 1);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/NumberPropertyValueObservable.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\n/**\n * @author 渔民小镇\n * @date 2024-04-17\n */\nabstract class NumberPropertyValueObservable extends AbstractPropertyValueObservable<Number> {\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/ObjectProperty.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\nimport lombok.ToString;\n\n/**\n * object - 属性具备监听特性。当值（引用）发生变更时，会触发监听事件。\n * <pre>{@code\n *         YourUser user = new YourUser();\n *\n *         var property = new ObjectProperty(user);\n *         // add listener monitor property object\n *         property.addListener((observable, oldValue, newValue) -> {\n *             log.info(\"oldValue:{}, newValue:{}\", oldValue, newValue);\n *         });\n *\n *         property.set(user); // does not trigger listeners\n *\n *         YourUser user2 = new YourUser();\n *         property.set(user2); // When the value changes,listeners are triggered\n *         property.get(); // value is user2\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\n@ToString\npublic final class ObjectProperty<T> extends AbstractPropertyValueObservable<T> {\n    T value;\n\n    public ObjectProperty() {\n        this(null);\n    }\n\n    public ObjectProperty(T value) {\n        this.value = value;\n    }\n\n    @Override\n    public T getValue() {\n        return get();\n    }\n\n    @Override\n    public void setValue(T value) {\n        this.set(value);\n    }\n\n    /**\n     * get current value\n     *\n     * @return current value\n     */\n    public T get() {\n        this.valid = true;\n        return this.value;\n    }\n\n    /**\n     * set current value\n     *\n     * @param newValue current new value\n     */\n    public void set(T newValue) {\n        if (this.value != newValue) {\n            this.value = newValue;\n            markInvalid();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyAbout.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * @author 渔民小镇\n * @date 2024-04-17\n */\nabstract class ChangeHelper<T> {\n    final PropertyValueObservable<T> observable;\n\n    /**\n     * 触发值变更事件\n     */\n    protected abstract void fireValueChangedEvent();\n\n    ChangeHelper(PropertyValueObservable<T> observable) {\n        this.observable = observable;\n    }\n\n    static <T> ChangeHelper<T> create(PropertyValueObservable<T> observable, PropertyChangeListener<? super T> listener) {\n        return new PropertySingleChange<>(observable, observable.getValue(), listener);\n    }\n\n    static <T> ChangeHelper<T> create(PropertyChangeListener<? super T> listener) {\n        return new PropertySingleChange<>(null, null, listener);\n    }\n\n    private static class PropertySingleChange<T> extends ChangeHelper<T> {\n        final PropertyChangeListener<? super T> listener;\n        T currentValue;\n\n        PropertySingleChange(PropertyValueObservable<T> observable, T currentValue, PropertyChangeListener<? super T> listener) {\n            super(observable);\n            this.currentValue = currentValue;\n            this.listener = listener;\n        }\n\n        @Override\n        protected void fireValueChangedEvent() {\n            final T oldValue = currentValue;\n            currentValue = observable.getValue();\n            final boolean changed = !Objects.equals(currentValue, oldValue);\n            if (changed) {\n                try {\n                    listener.changed(observable, oldValue, currentValue);\n                } catch (Exception e) {\n                    Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);\n                }\n            }\n        }\n\n        @Override\n        public final boolean equals(Object o) {\n            if (this == o) {\n                return true;\n            }\n\n            if (!(o instanceof PropertySingleChange<?> change)) {\n                return false;\n            }\n\n            return Objects.equals(listener, change.listener);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hashCode(listener);\n        }\n    }\n}\n\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class ChangeHelperList<T> {\n    List<ChangeHelper<? super T>> list;\n\n    void addListener(ChangeHelper<? super T> helper) {\n        if (Objects.isNull(this.list)) {\n            this.list = new CopyOnWriteArrayList<>();\n        }\n\n        this.list.add(helper);\n    }\n\n    void removeListener(PropertyChangeListener<? super T> listener) {\n        if (Objects.isNull(this.list) || Objects.isNull(listener)) {\n            return;\n        }\n\n        var helper = ChangeHelper.create(listener);\n        this.list.remove(helper);\n    }\n\n    void addListener(PropertyValueObservable<T> observable, PropertyChangeListener<? super T> listener) {\n\n        if (observable == null || listener == null) {\n            throw new NullPointerException();\n        }\n\n        var helper = ChangeHelper.create(observable, listener);\n        this.addListener(helper);\n    }\n\n    void fireValueChangedEvent() {\n        if (Objects.isNull(this.list)) {\n            return;\n        }\n\n        this.list.forEach(ChangeHelper::fireValueChangedEvent);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyChangeListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\n/**\n * 属性值变更事件监听器\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\n@FunctionalInterface\npublic interface PropertyChangeListener<T> {\n    /**\n     * 值变更监听\n     *\n     * @param observable current Property\n     * @param oldValue   oldValue\n     * @param newValue   newValue\n     */\n    void changed(PropertyValueObservable<? extends T> observable, T oldValue, T newValue);\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyValueObservable.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\n/**\n * 属性值对象\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\npublic interface PropertyValueObservable<T> {\n    /**\n     * add ChangeListener\n     *\n     * @param listener ChangeListener\n     */\n    void addListener(PropertyChangeListener<? super T> listener);\n\n    /**\n     * remove ChangeListener\n     *\n     * @param listener ChangeListener\n     */\n    void removeListener(PropertyChangeListener<? super T> listener);\n\n    /**\n     * get PropertyValue\n     *\n     * @return current value\n     */\n    T getValue();\n\n    /**\n     * set value\n     *\n     * @param value value\n     */\n    void setValue(T value);\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/StringProperty.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.beans.property;\n\nimport lombok.ToString;\n\nimport java.util.Objects;\n\n/**\n * String - 属性具备监听特性。当值发生变更时，会触发监听事件。\n * <pre>{@code\n *         var property = new StringProperty();\n *         // add listener monitor property object\n *         property.addListener((observable, oldValue, newValue) -> {\n *             log.info(\"oldValue:{}, newValue:{}\", oldValue, newValue);\n *         });\n *\n *         property.get(); // value is null\n *         property.set(\"22\"); // When the value changes,listeners are triggered\n *         property.get(); // value is \"22\"\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\n@ToString\npublic final class StringProperty extends AbstractPropertyValueObservable<String> {\n    String value;\n\n    public StringProperty() {\n        this(null);\n    }\n\n    public StringProperty(String value) {\n        this.value = value;\n    }\n\n    @Override\n    public String getValue() {\n        return get();\n    }\n\n    @Override\n    public void setValue(String value) {\n        this.set(value);\n    }\n\n    /**\n     * get current value\n     *\n     * @return current value\n     */\n    public String get() {\n        this.valid = true;\n        return this.value;\n    }\n\n    /**\n     * set current value\n     *\n     * @param newValue current new value\n     */\n    public void set(String newValue) {\n        if (!Objects.equals(this.value, newValue)) {\n            this.value = newValue;\n            markInvalid();\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - <a href=\"https://iohao.github.io/game/docs/kit/property_change_listener\">属性值变更监听</a>特性，属性可添加监听器，当某些属性值的发生变化时，触发监听器。\n *\n * @author 渔民小镇\n * @date 2024-04-17\n */\npackage com.iohao.game.common.kit.beans.property;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/collect/ListMultiMap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.collect;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * 线程安全的 ListMultiMap\n * <pre>\n *     value 为 list 集合实现\n * </pre>\n * for example\n * <pre>{@code\n * ListMultiMap<Integer, String> map = ListMultiMap.of();\n * map.put(1, \"a\");\n * map.put(1, \"a\");\n * map.put(1, \"b\");\n *\n * List<String> list2 = map.get(2); // is null\n * List<String> list2 = map.of(2); // is empty list\n *\n * list2.add(\"2 - a\");\n * list2.add(\"2 - a\");\n * map.sizeValue(); // sizeValue == 5\n *\n * map.containsValue(\"a\"); // true\n * map.containsValue(\"b\"); // true\n *\n * Set<Integer> keySet = this.map.keySet();\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-07\n */\npublic interface ListMultiMap<K, V> extends MultiMap<K, V> {\n    @Override\n    Map<K, List<V>> asMap();\n\n    /**\n     * 根据 key 来 get 一个元素，如果不存在就创建集合\n     * <pre>\n     *     get 的元素集合一定不为 null，如果不存在就新创建。\n     *\n     *     首次创建该 key 的集合时，会调用 consumer 并将新创建集合传入 consumer 中。\n     *\n     *     开发者有需要初始化的内容，可以通过 consumer 来实现\n     * </pre>\n     *\n     * @param key      key\n     * @param consumer consumer\n     * @return 集合\n     */\n    List<V> ofIfAbsent(K key, Consumer<List<V>> consumer);\n\n    @Override\n    default List<V> of(K key) {\n        return this.ofIfAbsent(key, null);\n    }\n\n    @Override\n    default List<V> get(K key) {\n        return asMap().get(key);\n    }\n\n    default Set<Map.Entry<K, List<V>>> entrySet() {\n        return this.asMap().entrySet();\n    }\n\n    /**\n     * 创建 ListMultiMap（框架内置实现）。请使用 {@link ListMultiMap#of()} 代替\n     *\n     * @param <K> k\n     * @param <V> v\n     * @return ListMultiMap\n     */\n    static <K, V> ListMultiMap<K, V> create() {\n        return of();\n    }\n\n    /**\n     * 创建 ListMultiMap（框架内置实现）\n     *\n     * @param <K> k\n     * @param <V> v\n     * @return ListMultiMap\n     */\n    static <K, V> ListMultiMap<K, V> of() {\n        return new NonBlockingListMultiMap<>();\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/collect/MultiMap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.collect;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-07\n */\ninterface MultiMap<K, V> {\n    /**\n     * 内部实现的真实 map\n     *\n     * @return map\n     */\n    Map<K, ? extends Collection<V>> asMap();\n\n    /**\n     * 根据 key 来 get 一个元素，如果不存在就创建集合\n     *\n     * @param key key\n     * @return collection 一定不为 null\n     */\n    Collection<V> of(K key);\n\n    /**\n     * get 一个元素\n     *\n     * @param key key\n     * @return collection\n     */\n    Collection<V> get(K key);\n\n    /**\n     * clear key 所对应的集合内的所有元素\n     *\n     * @param key key\n     * @return clear 后的集合，一定不为 null\n     */\n    default Collection<V> clearAll(K key) {\n        var collection = this.of(key);\n\n        collection.clear();\n\n        return collection;\n    }\n\n    /**\n     * map 集合的数目\n     *\n     * @return size\n     */\n    default int size() {\n        return this.asMap().size();\n    }\n\n    /**\n     * map 所有 value 集合的汇总\n     *\n     * @return value size\n     */\n    default int sizeValue() {\n        return this.asMap().values().stream()\n                .mapToInt(Collection::size)\n                .sum();\n    }\n\n    /**\n     * 向指定 key 的集合添加元素\n     *\n     * @param key   key\n     * @param value 元素\n     * @return true if this collection changed as a result of the call\n     */\n    default boolean put(K key, V value) {\n        var collection = this.of(key);\n        return collection.add(value);\n    }\n\n    default boolean isEmpty() {\n        return this.asMap().isEmpty();\n    }\n\n    default boolean containsKey(K key) {\n        return this.asMap().containsKey(key);\n    }\n\n    default boolean containsValue(V value) {\n        for (Collection<V> vs : this.asMap().values()) {\n            if (vs.contains(value)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    default void clear() {\n        this.asMap().clear();\n    }\n\n    default Set<K> keySet() {\n        return this.asMap().keySet();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/collect/NonBlockingListMultiMap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.collect;\n\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.*;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.function.Consumer;\n\n/**\n * 线程安全的 ListMultiMap\n * <pre>\n *     value 为 list 集合实现\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-07\n */\nfinal class NonBlockingListMultiMap<K, V> implements ListMultiMap<K, V> {\n    private final Map<K, List<V>> map = new NonBlockingHashMap<>();\n\n    @Override\n    public Map<K, List<V>> asMap() {\n        return this.map;\n    }\n\n    @Override\n    public List<V> ofIfAbsent(K key, Consumer<List<V>> consumer) {\n        var list = this.map.get(key);\n\n        if (Objects.isNull(list)) {\n            List<V> newValueList = new CopyOnWriteArrayList<>();\n            list = this.map.putIfAbsent(key, newValueList);\n\n            if (Objects.isNull(list)) {\n                List<V> initList = this.map.get(key);\n\n                // 首次初始化回调\n                Optional.ofNullable(consumer).ifPresent(c -> c.accept(initList));\n\n                return initList;\n            }\n        }\n\n        return list;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/collect/NonBlockingSetMultiMap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.collect;\n\nimport org.jctools.maps.NonBlockingHashMap;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.*;\nimport java.util.function.Consumer;\n\n/**\n * 线程安全的 SetMultiMap 实现\n * <pre>\n *     value 为 set 集合实现\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-07\n */\nfinal class NonBlockingSetMultiMap<K, V> implements SetMultiMap<K, V> {\n    private final NonBlockingHashMap<K, Set<V>> map = new NonBlockingHashMap<>();\n\n    @Override\n    public Map<K, Set<V>> asMap() {\n        return this.map;\n    }\n\n    @Override\n    public Set<V> ofIfAbsent(K key, Consumer<Set<V>> consumer) {\n        var set = this.map.get(key);\n\n        if (Objects.isNull(set)) {\n            Set<V> newValueSet = new NonBlockingHashSet<>();\n            set = this.map.putIfAbsent(key, newValueSet);\n\n            if (Objects.isNull(set)) {\n                Set<V> initSet = this.map.get(key);\n\n                // 首次初始化回调\n                Optional.ofNullable(consumer).ifPresent(c -> c.accept(initSet));\n\n                return initSet;\n            }\n        }\n\n        return set;\n    }\n\n    @Override\n    public Set<Map.Entry<K, Set<V>>> entrySet() {\n        return this.map.entrySet();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/collect/SetMultiMap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.collect;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * 线程安全的 SetMultiMap\n * <pre>\n *     value 为 set 集合实现\n * </pre>\n * for example\n * <pre>{@code\n * SetMultiMap<Integer, String> map = SetMultiMap.of();\n * map.put(1, \"a\");\n * map.put(1, \"a\");\n * map.put(1, \"b\");\n *\n * map.size(); // size == 1\n * map.sizeValue(); // sizeValue == 2\n *\n * Set<String> set2 = map.get(2); // is null\n * Set<String> set2 = map.of(2); // is empty set\n *\n * set2.add(\"2 - a\");\n * set2.add(\"2 - a\");\n *\n * map.sizeValue(); // sizeValue == 3\n *\n * map.containsValue(\"a\"); // true\n * map.containsValue(\"b\"); // true\n *\n * var collection = map.clearAll(1);\n * collection.isEmpty(); // true\n * map.size(); // size == 2\n *\n * Set<Integer> keySet = this.map.keySet();\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-07\n */\npublic interface SetMultiMap<K, V> extends MultiMap<K, V> {\n    @Override\n    Map<K, Set<V>> asMap();\n\n    /**\n     * 根据 key 来 get 一个元素，如果不存在就创建集合\n     * <pre>\n     *     get 的元素集合一定不为 null，如果不存在就新创建。\n     *\n     *     首次创建该 key 的集合时，会调用 consumer 并将新创建集合传入 consumer 中。\n     *\n     *     开发者有需要初始化的内容，可以通过 consumer 来实现\n     * </pre>\n     *\n     * @param key      key\n     * @param consumer consumer\n     * @return 集合\n     */\n    Set<V> ofIfAbsent(K key, Consumer<Set<V>> consumer);\n\n    @Override\n    default Set<V> of(K key) {\n        return this.ofIfAbsent(key, null);\n    }\n\n    @Override\n    default Set<V> get(K key) {\n        return asMap().get(key);\n    }\n\n    Set<Map.Entry<K, Set<V>>> entrySet();\n\n    /**\n     * 创建 SetMultiMap（框架内置实现）请使用 {@link SetMultiMap#of()} 代替\n     *\n     * @param <K> k\n     * @param <V> v\n     * @return SetMultiMap\n     */\n    static <K, V> SetMultiMap<K, V> create() {\n        return of();\n    }\n\n    /**\n     * 创建 SetMultiMap（框架内置实现）\n     *\n     * @param <K> k\n     * @param <V> v\n     * @return SetMultiMap\n     */\n    static <K, V> SetMultiMap<K, V> of() {\n        return new NonBlockingSetMultiMap<>();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/collect/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - 提供线程安全的多集合结构 MultiMap、ListMultiMap、SetMultiMap。\n *\n * @author 渔民小镇\n * @date 2024-06-04\n */\npackage com.iohao.game.common.kit.collect;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/CommonTaskListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent;\n\nimport java.util.concurrent.Executor;\n\n/**\n * 任务监听回调\n *\n * @author 渔民小镇\n * @date 2024-05-31\n * @since 21.9\n */\ninterface CommonTaskListener {\n    /**\n     * 是否触发 onUpdate 监听回调方法\n     *\n     * @return true 执行 onUpdate 方法\n     */\n    default boolean triggerUpdate() {\n        return true;\n    }\n\n    /**\n     * Timer 监听回调\n     */\n    void onUpdate();\n\n    /**\n     * 异常回调\n     * <pre>\n     *     当 triggerUpdate 或 onUpdate 方法抛出异常时，将会传递到这里\n     * </pre>\n     *\n     * @param e e\n     */\n    default void onException(Throwable e) {\n        System.err.println(e.getMessage());\n    }\n\n    /**\n     * 执行 onUpdate 的执行器\n     * <pre>\n     *     如果返回 null 将在 HashedWheelTimer 中执行。\n     *\n     *     如果有耗时的任务，比如涉及一些 io 操作的，建议指定执行器来执行当前回调（onUpdate 方法），以避免阻塞其他任务。\n     * </pre>\n     * 示例\n     * <pre>{@code\n     *     default Executor getExecutor() {\n     *         // 耗时任务，指定一个执行器来消费当前 onUpdate\n     *         return TaskKit.getCacheExecutor();\n     *     }\n     * }\n     * </pre>\n     *\n     * @return 当返回值为 null 时，将使用当前线程（默认 HashedWheelTimer）执行，否则使用该执行器来执行\n     */\n    default Executor getExecutor() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/DaemonThreadFactory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent;\n\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-02\n */\npublic final class DaemonThreadFactory extends ThreadCreator implements ThreadFactory {\n\n    public DaemonThreadFactory(String threadNamePrefix) {\n        super(threadNamePrefix);\n        this.setDaemon(true);\n    }\n\n    @Override\n    public Thread newThread(Runnable runnable) {\n        return createThread(runnable);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/FixedNameThreadFactory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent;\n\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n * @since 21.15\n */\npublic final class FixedNameThreadFactory extends ThreadCreator implements ThreadFactory {\n    public FixedNameThreadFactory(String threadNamePrefix) {\n        super(threadNamePrefix);\n        this.setDaemon(true);\n    }\n\n    @Override\n    public Thread newThread(Runnable runnable) {\n        return createThread(runnable);\n    }\n\n    @Override\n    protected String nextThreadName() {\n        return this.getThreadNamePrefix();\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/IntervalTaskListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent;\n\n/**\n * 调度任务监听，使用 HashedWheelTimer 来模拟 ScheduledExecutorService 调度。\n * <p>\n * 当 {@code isActive } 返回 true 时，才会执行 {@code triggerUpdate 和 onUpdate} 方法\n * <p>\n * example\n * <pre>{@code\n *     // 每分钟调用一次\n *     TaskKit.runIntervalMinute(() -> log.info(\"tick 1 Minute\"), 1);\n *     // 每 2 分钟调用一次\n *     TaskKit.runIntervalMinute(() -> log.info(\"tick 2 Minute\"), 2);\n *\n *     // 每 2 秒调用一次\n *     TaskKit.runInterval(() -> log.info(\"tick 2 Seconds\"), 2, TimeUnit.SECONDS);\n *     // 每 30 分钟调用一次\n *     TaskKit.runInterval(() -> log.info(\"tick 30 Minute\"), 30, TimeUnit.MINUTES);\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-01\n * @see TaskKit\n */\npublic interface IntervalTaskListener extends CommonTaskListener {\n    /**\n     * 是否活跃\n     *\n     * @return false 表示不活跃，会从监听列表中移除当前 TimerListener\n     */\n    default boolean isActive() {\n        return true;\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/OnceTaskListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent;\n\nimport com.iohao.game.common.kit.MoreKit;\nimport io.netty.util.Timeout;\nimport io.netty.util.TimerTask;\n\nimport java.util.concurrent.Executor;\n\n/**\n * Timer 监听回调，只执行 1 次。\n * <pre>\n *     当 {@code triggerUpdate } 返回 true 时，才会执行 {@code onUpdate} 方法。\n *\n *     默认情况下 triggerUpdate 返回值为 true，开发者可以通过控制 triggerUpdate 方法的返回值来决定是否执行 onUpdate 方法；\n * </pre>\n * example\n * <pre>{@code\n *     // 只执行一次，500、800 milliseconds 后\n *     TaskKit.runOnce(() -> log.info(\"500 delayMilliseconds\"), 500);\n *     TaskKit.runOnce(() -> log.info(\"800 delayMilliseconds\"), 800);\n *\n *     // 只执行一次，10 秒后执行\n *     TaskKit.runOnce(new YourOnceTaskListener(), 10, TimeUnit.SECONDS);\n *\n *     // 只执行一次，1500 Milliseconds 后执行，当 theTriggerUpdate 为 true 时，才执行 onUpdate\n *     boolean theTriggerUpdate = RandomKit.randomBoolean();\n *     TaskKit.runOnce(new OnceTaskListener() {\n *         @Override\n *         public void onUpdate() {\n *             log.info(\"1500 delayMilliseconds\");\n *         }\n *\n *         @Override\n *         public boolean triggerUpdate() {\n *             return theTriggerUpdate;\n *         }\n *\n *     }, 1500, TimeUnit.MILLISECONDS);\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-06\n * @see TaskKit\n * @see IntervalTaskListener\n * @since 21\n */\npublic interface OnceTaskListener extends TimerTask, TaskListener {\n    @Override\n    default void run(Timeout timeout) throws Exception {\n        Executor executor = this.getExecutor();\n        MoreKit.execute(executor, this::executeFlow);\n    }\n\n    private void executeFlow() {\n        try {\n            if (this.triggerUpdate()) {\n                this.onUpdate();\n            }\n        } catch (Throwable e) {\n            this.onException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent;\n\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.ExecutorKit;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.RuntimeKit;\nimport com.iohao.game.common.kit.collect.SetMultiMap;\nimport io.netty.util.HashedWheelTimer;\nimport io.netty.util.Timeout;\nimport io.netty.util.Timer;\nimport io.netty.util.TimerTask;\nimport lombok.Getter;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.*;\nimport java.util.function.Supplier;\n\n/**\n * 任务消费相关的内部工具类，开发者不要用在耗时 io 的任务上\n * <p>\n * example - 使用其他线程执行任务\n * <pre>{@code\n *     TaskKit.execute(()->{\n *         log.info(\"你的逻辑\");\n *     });\n * }\n * </pre>\n * example - netty TimerTask\n * <pre>{@code\n *     // 3 秒后执行\n *     TaskKit.newTimeout(new TimerTask() {\n *         @Override\n *         public void run(Timeout timeout) {\n *             log.info(\"3-newTimeout : {}\", timeout);\n *         }\n *     }, 3, TimeUnit.SECONDS);\n * }\n * </pre>\n * example - TaskListener 监听回调。内部使用 HashedWheelTimer 来模拟 ScheduledExecutorService 调度\n * <pre>{@code\n *     // 只执行一次，2 秒后执行\n *     TaskKit.runOnce(() -> log.info(\"2 Seconds\"), 2, TimeUnit.SECONDS);\n *     // 只执行一次，1 分钟后执行\n *     TaskKit.runOnce(() -> log.info(\"1 Minute\"), 1, TimeUnit.MINUTES)\n *     // 只执行一次，500、800 milliseconds 后\n *     TaskKit.runOnce(() -> log.info(\"500 delayMilliseconds\"), 500);\n *     TaskKit.runOnce(() -> log.info(\"800 delayMilliseconds\"), 800);\n *\n *     // 每分钟调用一次\n *     TaskKit.runIntervalMinute(() -> log.info(\"tick 1 Minute\"), 1);\n *     // 每 2 分钟调用一次\n *     TaskKit.runIntervalMinute(() -> log.info(\"tick 2 Minute\"), 2);\n *\n *     // 每 2 秒调用一次\n *     TaskKit.runInterval(() -> log.info(\"tick 2 Seconds\"), 2, TimeUnit.SECONDS);\n *     // 每 30 分钟调用一次\n *     TaskKit.runInterval(() -> log.info(\"tick 30 Minute\"), 30, TimeUnit.MINUTES);\n * }\n * </pre>\n * example - TaskListener - 高级用法\n * <pre>{@code\n *      //【示例 - 移除任务】每秒调用一次，当 hp 为 0 时就移除当前 Listener\n *     TaskKit.runInterval(new IntervalTaskListener() {\n *         int hp = 2;\n *\n *         @Override\n *         public void onUpdate() {\n *             hp--;\n *             log.info(\"剩余 hp:2-{}\", hp);\n *         }\n *\n *         @Override\n *         public boolean isActive() {\n *             // 当返回 false 则表示不活跃，会从监听列表中移除当前 Listener\n *             return hp != 0;\n *         }\n *     }, 1, TimeUnit.SECONDS);\n *\n *     //【示例 - 跳过执行】每秒调用一次，当 triggerUpdate 返回值为 true，即符合条件时才执行 onUpdate 方法\n *     TaskKit.runInterval(new IntervalTaskListener() {\n *         int hp;\n *\n *         @Override\n *         public void onUpdate() {\n *             log.info(\"current hp:{}\", hp);\n *         }\n *\n *         @Override\n *         public boolean triggerUpdate() {\n *             hp++;\n *             // 当返回值为 true 时，会执行 onUpdate 方法\n *             return hp % 2 == 0;\n *         }\n *     }, 1, TimeUnit.SECONDS);\n *\n *     //【示例 - 指定线程执行器】每秒调用一次\n *     // 如果有耗时的任务，比如涉及一些 io 操作的，建议指定执行器来执行当前回调（onUpdate 方法），以避免阻塞其他任务。\n *     TaskKit.runInterval(new IntervalTaskListener() {\n *         @Override\n *         public void onUpdate() {\n *             log.info(\"执行耗时的 IO 任务，开始\");\n *\n *             try {\n *                 TimeUnit.SECONDS.sleep(3);\n *             } catch (InterruptedException e) {\n *                 throw new RuntimeException(e);\n *             }\n *\n *             log.info(\"执行耗时的 IO 任务，结束\");\n *         }\n *\n *         @Override\n *         public Executor getExecutor() {\n *             // 指定执行器来执行当前回调（onUpdate 方法），以避免阻塞其他任务。\n *             return TaskKit.getCacheExecutor();\n *         }\n *     }, 1, TimeUnit.SECONDS);\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-02\n */\n@Slf4j\n@UtilityClass\npublic class TaskKit {\n    /** 执行一些没有 io 操作的逻辑 */\n    private Timer wheelTimer = new HashedWheelTimer();\n    /** 内置的 cacheExecutor 执行器 */\n    @Getter\n    final ExecutorService cacheExecutor = ExecutorKit.newFixedThreadPool(RuntimeKit.availableProcessors2n, \"ioGameThread-\");\n    /** 虚拟线程执行器 */\n    @Getter\n    final ExecutorService virtualExecutor = ExecutorKit.newVirtualExecutor(\"ioGameVirtual\");\n    final SetMultiMap<TickTimeUnit, IntervalTaskListener> intervalTaskListenerMap = SetMultiMap.of();\n\n    record TickTimeUnit(long tick, TimeUnit timeUnit) {\n    }\n\n    /**\n     * set HashedWheelTimer\n     * <pre>{@code\n     * TaskKit.setTimer(new HashedWheelTimer(17, TimeUnit.MILLISECONDS));\n     * }\n     * </pre>\n     *\n     * @param timer Timer\n     * @since 21.23\n     */\n    public void setTimer(Timer timer) {\n        Objects.requireNonNull(timer);\n\n        var oldTimer = wheelTimer;\n        wheelTimer = timer;\n        oldTimer.stop();\n    }\n\n    /**\n     * 使用其他线程执行任务\n     *\n     * @param command 任务\n     */\n    public void execute(Runnable command) {\n        cacheExecutor.execute(command);\n    }\n\n    /**\n     * 使用虚拟线程执行任务\n     *\n     * @param command 任务\n     */\n    public void executeVirtual(Runnable command) {\n        virtualExecutor.execute(command);\n    }\n\n    /**\n     * 返回一个 CompletableFuture，该任务会在 virtualExecutor（虚拟线程） 中异步运行，结果将从 Supplier 中获得\n     *\n     * @param supplier supplier\n     * @param <U>      u\n     * @return CompletableFuture\n     * @see TaskKit#virtualExecutor\n     */\n    public <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {\n        return CompletableFuture.supplyAsync(supplier, virtualExecutor);\n    }\n\n    /**\n     * 延迟一定时间后执行任务；\n     *\n     * @param task  任务\n     * @param delay 延迟时间\n     * @param unit  延迟时间单位\n     * @return Timeout\n     */\n    public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {\n        return wheelTimer.newTimeout(task, delay, unit);\n    }\n\n    /**\n     * 添加 OnceTaskListener 监听回调，只会执行一次\n     *\n     * @param taskListener taskListener\n     * @param delay        延迟时间\n     * @param unit         延迟时间单位\n     */\n    public void runOnce(OnceTaskListener taskListener, long delay, TimeUnit unit) {\n        newTimeout(taskListener, delay, unit);\n    }\n\n    /**\n     * 一秒后执行 OnceTaskListener 监听回调，只会执行一次\n     *\n     * @param taskListener taskListener\n     */\n    public void runOnceSecond(OnceTaskListener taskListener) {\n        runOnce(taskListener, 1, TimeUnit.SECONDS);\n    }\n\n    /**\n     * 添加 OnceTaskListener 监听回调，只会执行一次\n     *\n     * @param taskListener      taskListener\n     * @param delayMilliseconds delayMilliseconds\n     */\n    public void runOnceMillis(OnceTaskListener taskListener, long delayMilliseconds) {\n        runOnce(taskListener, delayMilliseconds, TimeUnit.MILLISECONDS);\n    }\n\n    /**\n     * 添加调度任务监听\n     *\n     * @param taskListener 调度任务监听\n     * @param tickMinute   每 tickMinute 分钟，会调用一次监听\n     */\n    public void runIntervalMinute(IntervalTaskListener taskListener, long tickMinute) {\n        runInterval(taskListener, tickMinute, TimeUnit.MINUTES);\n    }\n\n    /**\n     * 添加任务监听回调\n     * <pre>\n     *     每 tick 时间单位，会调用一次任务监听\n     * </pre>\n     *\n     * @param taskListener 任务监听\n     * @param tick         tick 时间间隔；每 tick 时间间隔，会调用一次监听\n     * @param timeUnit     tick 时间单位\n     */\n    public void runInterval(IntervalTaskListener taskListener, long tick, TimeUnit timeUnit) {\n        TickTimeUnit tickTimeUnit = new TickTimeUnit(tick, timeUnit);\n\n        Set<IntervalTaskListener> intervalTaskListeners = intervalTaskListenerMap.get(tickTimeUnit);\n\n        // 无锁化\n        if (CollKit.isEmpty(intervalTaskListeners)) {\n            intervalTaskListeners = intervalTaskListenerMap.ofIfAbsent(tickTimeUnit, (initSet) -> {\n                // 使用 HashedWheelTimer 来模拟 ScheduledExecutorService 调度\n                foreverTimerTask(tick, timeUnit, initSet);\n            });\n        }\n\n        intervalTaskListeners.add(taskListener);\n    }\n\n    private void foreverTimerTask(long tick, TimeUnit timeUnit, Set<IntervalTaskListener> set) {\n        // 启动定时器\n        TaskKit.newTimeout(new TimerTask() {\n            @Override\n            public void run(Timeout timeout) {\n                if (set.isEmpty()) {\n                    TaskKit.newTimeout(this, tick, timeUnit);\n                    return;\n                }\n\n                set.forEach(intervalTaskListener -> {\n                    var executor = intervalTaskListener.getExecutor();\n                    // 如果指定了执行器，就将执行流程放到执行器中，否则使用当前线程\n                    MoreKit.execute(executor, () -> executeFlowTimerListener(intervalTaskListener, set));\n                });\n\n                TaskKit.newTimeout(this, tick, timeUnit);\n            }\n        }, tick, timeUnit);\n    }\n\n    private void executeFlowTimerListener(IntervalTaskListener taskListener, Set<IntervalTaskListener> set) {\n        try {\n            // 移除不活跃的监听\n            if (!taskListener.isActive()) {\n                set.remove(taskListener);\n                return;\n            }\n\n            if (taskListener.triggerUpdate()) {\n                taskListener.onUpdate();\n            }\n        } catch (Throwable e) {\n            taskListener.onException(e);\n        }\n    }\n\n    public void stop() {\n        wheelTimer.stop();\n        cacheExecutor.shutdown();\n        virtualExecutor.shutdown();\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent;\n\n/**\n * 任务监听回调，使用场景有：一次性延时任务、任务调度、轻量可控的延时任务、轻量的定时入库辅助功能 ...等其他扩展场景。\n * <a href=\"https://iohao.github.io/game/docs/kit/task_kit\">相关文档</a>\n *\n * <pre>\n *     这些使用场景都有一个共同特点，即监听回调。接口提供了 4 个方法，如下\n *     1. {@link TaskListener#onUpdate()}，监听回调\n *     2. {@link TaskListener#triggerUpdate()}，是否触发 {@link TaskListener#onUpdate()} 监听回调方法\n *     3. {@link TaskListener#onException(Throwable)} ，异常回调。在执行 {@link TaskListener#triggerUpdate()} 和 {@link TaskListener#onUpdate()} 方法时，如果触发了异常，异常将被该方法捕获。\n *     4. {@link TaskListener#getExecutor()}，指定执行器来执行上述方法，目的是不占用业务线程。\n * </pre>\n *\n *\n * @author 渔民小镇\n * @date 2023-12-06\n * @since 21.9\n */\npublic interface TaskListener extends CommonTaskListener {\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/ThreadCreator.java",
    "content": "/*\n * Copyright 2002-2013 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.iohao.game.common.kit.concurrent;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * copy from springframework\n *\n * @author 渔民小镇\n * @date 2023-04-02\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ThreadCreator {\n\n    String threadNamePrefix;\n\n    int threadPriority = Thread.NORM_PRIORITY;\n\n    boolean daemon = false;\n\n    ThreadGroup threadGroup;\n\n    final AtomicInteger threadCount = new AtomicInteger();\n\n    public ThreadCreator(String threadNamePrefix) {\n        this.threadNamePrefix = threadNamePrefix;\n    }\n\n    public void setThreadGroupName(String name) {\n        this.threadGroup = new ThreadGroup(name);\n    }\n\n    public Thread createThread(Runnable runnable) {\n        Thread thread = new Thread(getThreadGroup(), runnable, nextThreadName());\n        thread.setPriority(threadPriority);\n        thread.setDaemon(daemon);\n        return thread;\n    }\n\n    protected String nextThreadName() {\n        String format = \"%s-%d\";\n        return String.format(format, this.threadNamePrefix, this.threadCount.incrementAndGet());\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/AbstractThreadExecutorRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\nimport com.iohao.game.common.kit.concurrent.FixedNameThreadFactory;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.concurrent.*;\n\n/**\n * 线程执行器管理域父类\n *\n * @author 渔民小镇\n * @date 2023-12-01\n */\n@FieldDefaults(level = AccessLevel.PROTECTED)\nabstract sealed class AbstractThreadExecutorRegion implements ThreadExecutorRegion\n        permits\n        UserThreadExecutorRegion,\n        UserVirtualThreadExecutorRegion,\n        SimpleThreadExecutorRegion {\n\n    /** 线程执行器 */\n    final ThreadExecutor[] threadExecutors;\n\n    AbstractThreadExecutorRegion(String threadName, int executorSize) {\n        this.threadExecutors = new ThreadExecutor[executorSize];\n\n        for (int i = 0; i < executorSize; i++) {\n            // 线程名：name-线程总数-当前线程编号\n            int threadNo = i + 1;\n            String threadNamePrefix = String.format(\"%s-%s-%s\", threadName, executorSize, threadNo);\n            var executor = this.createExecutorService(threadNamePrefix);\n            this.threadExecutors[i] = new ThreadExecutor(threadNamePrefix, executor, threadNo);\n        }\n    }\n\n    protected ExecutorService createExecutorService(String name) {\n        ThreadFactory threadFactory = new FixedNameThreadFactory(name);\n\n        return new ThreadPoolExecutor(1, 1,\n                0L, TimeUnit.MILLISECONDS,\n                new LinkedBlockingQueue<>(),\n                threadFactory);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/ExecutorRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\n/**\n * 执行器管理域，管理着 {@link ThreadExecutorRegion} （线程执行器管理域）的实现类\n * <pre>\n *     管理着 {@link UserThreadExecutorRegion}、{@link UserVirtualThreadExecutorRegion}、{@link SimpleThreadExecutorRegion}。\n *     即使同进程启动了多个逻辑服，也会共享线程相关资源，从而避免创建过多的线程。\n *\n *     {@link UserThreadExecutorRegion} - 用户线程执行器管理域\n *     该执行器主要用于消费 action 业务，或者说消费玩家相关的业务。\n *     通过 userId 来得到对应的 ThreadExecutor 执行业务，从而避免并发问题。\n *\n *     {@link UserVirtualThreadExecutorRegion} - 用户虚拟线程执行器\n *     该执行器主要用于消费 io 的相关业务（如 DB 入库）。\n *\n *     {@link SimpleThreadExecutorRegion} - 简单的线程执行器管理域\n *     该执行器与 {@link UserThreadExecutorRegion} 类似。\n *     可通过 index 来得到对应的 ThreadExecutor 执行业务，从而避免并发问题。\n *     如果业务是计算密集型的，又不想占用 {@link UserThreadExecutorRegion} 线程资源时，可使用该执行器。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-01-11\n * @see UserThreadExecutorRegion 用户线程执行器管理域\n * @see UserVirtualThreadExecutorRegion 用户虚拟线程执行器\n * @see SimpleThreadExecutorRegion 简单的线程执行器管理域\n */\npublic interface ExecutorRegion {\n    /**\n     * user 线程执行器管理域\n     *\n     * @return user 线程执行器管理域\n     */\n    ThreadExecutorRegion getUserThreadExecutorRegion();\n\n    /**\n     * 用户虚拟线程执行器\n     *\n     * @return 用户虚拟线程执行器\n     */\n    default ThreadExecutorRegion getUserVirtualThreadExecutorRegion() {\n        return UserVirtualThreadExecutorRegion.me();\n    }\n\n    /**\n     * 简单的线程执行器管理域\n     *\n     * @return 简单的线程执行器管理域\n     */\n    ThreadExecutorRegion getSimpleThreadExecutorRegion();\n\n    /**\n     * user 线程执行器管理域\n     *\n     * @param index index\n     * @return user 线程执行器管理域\n     */\n    default ThreadExecutor getUserThreadExecutor(long index) {\n        return this.getUserThreadExecutorRegion().getThreadExecutor(index);\n    }\n\n    /**\n     * 用户虚拟线程执行器\n     *\n     * @param index index\n     * @return 用户虚拟线程执行器\n     */\n    default ThreadExecutor getUserVirtualThreadExecutor(long index) {\n        return this.getUserVirtualThreadExecutorRegion().getThreadExecutor(index);\n    }\n\n    /**\n     * 简单的线程执行器管理域\n     *\n     * @param index index\n     * @return 简单的线程执行器管理域\n     */\n    default ThreadExecutor getSimpleThreadExecutor(long index) {\n        return this.getSimpleThreadExecutorRegion().getThreadExecutor(index);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/ExecutorRegionKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.function.Supplier;\n\n/**\n * ExecutorRegion 工具类，起到类似代理的作用。\n * <pre>\n *     即使同进程启动了多个逻辑服，也会共享线程相关资源，从而避免创建过多的线程。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-01-11\n * @see UserThreadExecutorRegion 用户线程执行器管理域\n * @see UserVirtualThreadExecutorRegion 用户虚拟线程执行器\n * @see SimpleThreadExecutorRegion 简单的线程执行器管理域\n * @see ExecutorRegion 线程执行器 region\n */\n@UtilityClass\npublic class ExecutorRegionKit {\n    /** 定制线程策略 */\n    @Setter\n    Supplier<ExecutorRegion> executorRegionSupplier = () -> new ExecutorRegion() {\n        final UserThreadExecutorRegion userThreadExecutorRegion = new UserThreadExecutorRegion();\n        final SimpleThreadExecutorRegion simpleThreadExecutorRegion = SimpleThreadExecutorRegion.me();\n\n        @Override\n        public ThreadExecutorRegion getUserThreadExecutorRegion() {\n            return this.userThreadExecutorRegion;\n        }\n\n        @Override\n        public ThreadExecutorRegion getSimpleThreadExecutorRegion() {\n            return simpleThreadExecutorRegion;\n        }\n    };\n\n    @Setter\n    @Getter\n    ExecutorRegion executorRegion = executorRegionSupplier.get();\n\n    public ExecutorRegion createExecutorRegion() {\n        return executorRegionSupplier.get();\n    }\n\n    /**\n     * 简单的线程执行器管理域\n     *\n     * @param index index\n     * @return 简单的线程执行器管理域\n     */\n    public ThreadExecutor getSimpleThreadExecutor(long index) {\n        return executorRegion.getSimpleThreadExecutor(index);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/SimpleThreadExecutorRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\nimport com.iohao.game.common.kit.RuntimeKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 简单的线程执行器管理域\n * <pre>\n *     执行器的数量与 Runtime.getRuntime().availableProcessors() 相同。\n *\n *     SimpleThreadExecutorRegion - 简单的线程执行器管理域\n *     该执行器与 {@link UserThreadExecutorRegion} 类似。\n *     可通过 index 来得到对应的 ThreadExecutor 执行业务，从而避免并发问题。\n *     如果业务是计算密集型的，又不想占用 {@link UserThreadExecutorRegion} 线程资源时，可使用该执行器。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-01\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class SimpleThreadExecutorRegion extends AbstractThreadExecutorRegion {\n    final int executorLength;\n\n    SimpleThreadExecutorRegion() {\n        super(\"Simple\", RuntimeKit.availableProcessors);\n        executorLength = RuntimeKit.availableProcessors;\n    }\n\n    @Override\n    public ThreadExecutor getThreadExecutor(long index) {\n        var i = (int) (index % this.executorLength);\n        return this.threadExecutors[i];\n    }\n\n    static SimpleThreadExecutorRegion me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final SimpleThreadExecutorRegion ME = new SimpleThreadExecutorRegion();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/ThreadExecutor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * 线程执行器信息\n *\n * @param name     线程执行器名\n * @param executor 线程执行器\n * @param threadNo 编号\n * @author 渔民小镇\n * @date 2023-11-30\n */\n@Slf4j\npublic record ThreadExecutor(String name, Executor executor, int threadNo) {\n    /**\n     * 在将来的某个时间执行给定的命令。\n     * <pre>\n     *     注意，此方法需要开发者自行捕获 command 中的异常（换句话说就是，不要让 Runnable 在执行时拋出异常），\n     *     否则可能会引起 executor thread 损坏，从而使线程不可用。\n     *\n     *     如果无法确定 Runnable 在执行时是否会拋出异常，可以考虑使用 executeTry 方法。\n     * </pre>\n     *\n     * @param command 命令\n     * @see ThreadExecutor#executeTry(Runnable)\n     */\n    public void execute(Runnable command) {\n        this.executor.execute(command);\n    }\n\n    /**\n     * 在将来的某个时间执行给定的命令。\n     *\n     * @param command 命令\n     */\n    public void executeTry(Runnable command) {\n        this.executor.execute(() -> {\n            try {\n                command.run();\n            } catch (Throwable e) {\n                log.error(e.getMessage(), e);\n            }\n        });\n    }\n\n    public int getWorkQueue() {\n        if (this.executor instanceof ThreadPoolExecutor threadPoolExecutor) {\n            return threadPoolExecutor.getQueue().size();\n        }\n\n        return 0;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/ThreadExecutorRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\n/**\n * 线程执行器管理域\n * <pre>\n *     支持指定执行器来消费任务\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-01\n */\npublic interface ThreadExecutorRegion {\n    /**\n     * 根据 index 获取对应的 Executor\n     *\n     * @param index index 不能是负数\n     * @return Executor 线程执行器\n     */\n    ThreadExecutor getThreadExecutor(long index);\n\n    /**\n     * 根据 index 获取对应的 Executor 来执行任务\n     *\n     * @param runnable 任务\n     * @param index    index\n     */\n    default void execute(Runnable runnable, long index) {\n        this.getThreadExecutor(index).execute(runnable);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/UserThreadExecutorRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\nimport com.iohao.game.common.kit.RuntimeKit;\n\n/**\n * 用户线程执行器管理域\n * <pre>\n *     执行器具体数量是不大于 Runtime.getRuntime().availableProcessors() 的 2 次幂。\n *     当 availableProcessors 的值分别为 4、8、12、16、32 时，对应的数量则是 4、8、8、16、32。\n *\n *     4、8、12、16、32 （availableProcessors 的值）\n *     4、8、 8、16、32 （对应的数量）\n * </pre>\n * <pre>\n *     UserThreadExecutorRegion - 用户线程执行器管理域\n *     该执行器主要用于消费 action 业务，或者说消费玩家相关的业务。\n *     通过 userId 来得到对应的 ThreadExecutor 执行业务，从而避免并发问题。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-01\n */\nfinal class UserThreadExecutorRegion extends AbstractThreadExecutorRegion {\n    final int executorLength;\n\n    UserThreadExecutorRegion() {\n        super(\"User\", RuntimeKit.availableProcessors2n);\n        this.executorLength = RuntimeKit.availableProcessors2n - 1;\n    }\n\n    /**\n     * 根据 userId 获取对应的 Executor\n     *\n     * @param userId userId\n     * @return Executor 任务执行器\n     */\n    @Override\n    public ThreadExecutor getThreadExecutor(long userId) {\n        int index = (int) (userId & this.executorLength);\n        return this.threadExecutors[index];\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/UserVirtualThreadExecutorRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.executor;\n\nimport com.iohao.game.common.kit.ExecutorKit;\nimport com.iohao.game.common.kit.RuntimeKit;\n\nimport java.util.concurrent.ExecutorService;\n\n/**\n * 用户虚拟线程执行器\n * <pre>\n *     该执行器主要用于消费 io 的相关业务（如 DB 入库）。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-19\n */\nfinal class UserVirtualThreadExecutorRegion extends AbstractThreadExecutorRegion {\n    final int executorLength;\n\n    UserVirtualThreadExecutorRegion(String threadName) {\n        super(threadName, RuntimeKit.availableProcessors2n);\n        this.executorLength = RuntimeKit.availableProcessors2n - 1;\n    }\n\n    /**\n     * 根据 userId 获取对应的 Executor\n     *\n     * @param userId userId\n     * @return Executor 任务执行器\n     */\n    @Override\n    public ThreadExecutor getThreadExecutor(long userId) {\n        int index = (int) (userId & this.executorLength);\n        return this.threadExecutors[index];\n    }\n\n    @Override\n    protected ExecutorService createExecutorService(String name) {\n        return ExecutorKit.newVirtualExecutor(name);\n    }\n\n    static UserVirtualThreadExecutorRegion me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final UserVirtualThreadExecutorRegion ME = new UserVirtualThreadExecutorRegion(\"UserVirtual\");\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - <a href=\"https://iohao.github.io/game/docs/overall/thread_executor\">ioGame 线程相关</a>的线程执行器：用户线程执行器、用户虚拟线程执行器、简单的线程执行器管理域。\n *\n * @author 渔民小镇\n * @date 2024-06-01\n */\npackage com.iohao.game.common.kit.concurrent.executor;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - 线程执行器，任务监听回调、单次任务、延时任务、任务调度、轻量可控的延时任务、轻量的定时入库辅助功能。\n * <p>\n * 相关文档\n * <pre>\n *     <a href=\"https://iohao.github.io/game/docs/kit/task_kit\">TaskKit 任务相关</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-06-01\n */\npackage com.iohao.game.common.kit.concurrent;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/timer/delay/DelayTask.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.timer.delay;\n\nimport com.iohao.game.common.kit.concurrent.TaskListener;\n\nimport java.time.Duration;\n\n/**\n * <a href=\"https://iohao.github.io/game/docs/kit/delay_task\">轻量可控的延时任务</a>，任务到达指定时间后会执行、任务可取消、任务可增加延时时间\n *\n * @author 渔民小镇\n * @date 2024-09-01\n * @since 21.16\n */\npublic interface DelayTask {\n\n    /**\n     * get taskId\n     *\n     * @return taskId\n     */\n    String getTaskId();\n\n    /**\n     * 获取任务监听对象\n     *\n     * @param <T> t\n     * @return 任务监听\n     */\n    <T extends TaskListener> T getTaskListener();\n\n    /**\n     * 是否活跃的任务\n     *\n     * @return true 活跃的\n     */\n    boolean isActive();\n\n    /**\n     * 取消任务\n     */\n    void cancel();\n\n    /**\n     * 剩余的延时时间 millis\n     *\n     * @return 剩余的延时时间 millis\n     */\n    long getMillis();\n\n    /**\n     * 增加延时时间\n     *\n     * @param duration duration\n     * @return DelayTask\n     */\n    default DelayTask plusTime(Duration duration) {\n        return this.plusTimeMillis(duration.toMillis());\n    }\n\n    /**\n     * 增加延时时间\n     * <p>\n     * for example\n     * <pre>{@code\n     *     DelayTask delayTask = ...;\n     *     delayTask.plusTimeMillis(500);  // 增加 0.5 秒的延时时间\n     *     delayTask.plusTimeMillis(-500); // 减少 0.5 秒的延时时间\n     * }</pre>\n     *\n     * @param millis millis（当为负数时，表示减少延时时间）\n     * @return DelayTask\n     */\n    DelayTask plusTimeMillis(long millis);\n\n    /**\n     * 减少延时时间\n     * <p>\n     * for example\n     * <pre>{@code\n     *     DelayTask delayTask = ...;\n     *     delayTask.minusTimeMillis(500);  // 减少 0.5 秒的延时时间\n     *     delayTask.minusTimeMillis(-500); // 增加 0.5 秒的延时时间\n     * }</pre>\n     *\n     * @param millis millis（当为负数时，表示增加延时时间）\n     * @return DelayTask\n     */\n    default DelayTask minusTimeMillis(long millis) {\n        return this.plusTimeMillis(-millis);\n    }\n\n    /**\n     * 减少延时时间\n     *\n     * @param duration duration\n     * @return DelayTask\n     */\n    default DelayTask minusTime(Duration duration) {\n        return this.minusTimeMillis(duration.toMillis());\n    }\n\n    /**\n     * 启动延时任务\n     *\n     * @return DelayTask\n     */\n    DelayTask task();\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/timer/delay/DelayTaskKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.timer.delay;\n\nimport com.iohao.game.common.kit.concurrent.TaskListener;\nimport lombok.Getter;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.function.Consumer;\n\n/**\n * 轻量可控的延时任务工具类\n *\n * @author 渔民小镇\n * @date 2024-09-01\n * @since 21.16\n */\n@UtilityClass\npublic class DelayTaskKit {\n    /** 轻量可控延时任务域接口 */\n    @Getter\n    DelayTaskRegion delayTaskRegion = new SimpleDelayTaskRegion();\n\n    /**\n     * 设置轻量可控的延时任务域\n     *\n     * @param delayTaskRegion delayTaskRegion\n     */\n    public void setDelayTaskRegion(DelayTaskRegion delayTaskRegion) {\n        Objects.requireNonNull(delayTaskRegion);\n\n        var delayTaskRegionOld = DelayTaskKit.delayTaskRegion;\n        DelayTaskKit.delayTaskRegion = delayTaskRegion;\n\n        if (delayTaskRegionOld instanceof DelayTaskRegionEnhance stop) {\n            stop.stop();\n        }\n    }\n\n    /**\n     * 通过 taskId 取消任务\n     *\n     * @param taskId taskId\n     */\n    public void cancel(String taskId) {\n        delayTaskRegion.cancel(taskId);\n    }\n\n    /**\n     * get Optional DelayTask by taskId\n     *\n     * @param taskId taskId\n     * @return Optional DelayTask\n     */\n    public Optional<DelayTask> optional(String taskId) {\n        return delayTaskRegion.optional(taskId);\n    }\n\n    /**\n     * 如果 taskId 存在，就执行给定操作\n     *\n     * @param taskId   taskId\n     * @param consumer 给定操作\n     */\n    public void ifPresent(String taskId, Consumer<DelayTask> consumer) {\n        DelayTaskKit.optional(taskId).ifPresent(consumer);\n    }\n\n    /**\n     * 创建一个轻量可控的延时任务\n     *\n     * @param taskListener 任务监听回调\n     * @return 轻量可控的延时任务\n     */\n    public DelayTask of(TaskListener taskListener) {\n        return delayTaskRegion.of(taskListener);\n    }\n\n    /**\n     * 创建一个轻量可控的延时任务\n     *\n     * @param taskId       taskId （如果 taskId 相同，会覆盖之前的延时任务）\n     * @param taskListener 任务监听回调\n     * @return 轻量可控的延时任务\n     */\n    public DelayTask of(String taskId, TaskListener taskListener) {\n        return delayTaskRegion.of(taskId, taskListener);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/timer/delay/DelayTaskRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.timer.delay;\n\nimport com.iohao.game.common.kit.concurrent.TaskListener;\n\nimport java.util.Optional;\n\n/**\n * 轻量可控延时任务域接口，负责轻量可控延时任务的创建、获取、取消、统计任务数量 ...等相关操作。\n *\n * @author 渔民小镇\n * @date 2024-09-01\n * @since 21.16\n */\npublic interface DelayTaskRegion {\n\n    /**\n     * 通过 taskId 获取一个可控的延时任务 Optional\n     *\n     * @param taskId taskId\n     * @return DelayTask Optional\n     */\n    Optional<DelayTask> optional(String taskId);\n\n    /**\n     * 根据 taskId 取消可控延时任务的执行。\n     *\n     * @param taskId taskId\n     */\n    void cancel(String taskId);\n\n    /**\n     * 统计当前延时任务的数量\n     *\n     * @return 当前延时任务数量\n     */\n    int count();\n\n    /**\n     * 创建一个可控的延时任务，并设置任务监听回调。\n     * <pre>{@code\n     * DelayTask delayTask = of(taskListener);\n     * // 启动延时任务\n     * delayTask.task();\n     * }\n     * </pre>\n     *\n     * @param taskListener 任务监听回调\n     * @return 可控的延时任务\n     */\n    DelayTask of(TaskListener taskListener);\n\n    /**\n     * 创建一个可控的延时任务，并设置 taskId 和任务监听回调\n     * <pre>{@code\n     * DelayTask delayTask = of(taskId, taskListener);\n     * // 启动延时任务\n     * delayTask.task();\n     * }\n     * </pre>\n     *\n     * @param taskId       taskId\n     * @param taskListener 任务监听回调\n     * @return 可控的延时任务\n     */\n    DelayTask of(String taskId, TaskListener taskListener);\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/timer/delay/InternalDelayAbout.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.concurrent.timer.delay;\n\nimport com.iohao.game.common.kit.concurrent.IntervalTaskListener;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.common.kit.concurrent.TaskListener;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.LongAdder;\nimport java.util.function.Consumer;\n\ninterface DelayTaskExecutor extends DelayTask {\n    /**\n     * 是否触发 onUpdate 监听回调方法\n     *\n     * @return true 执行 onUpdate 方法\n     */\n    boolean triggerUpdate();\n\n    /**\n     * Timer 监听回调\n     */\n    void onUpdate();\n\n    /**\n     * 异常回调，当 triggerUpdate 或 onUpdate 方法抛出异常时，将会传递到这里\n     *\n     * @param e e\n     */\n    default void onException(Throwable e) {\n        System.err.println(e.getMessage());\n    }\n\n    /**\n     * 执行 onUpdate 的执行器\n     *\n     * @return 当返回值为 null 时，将使用当前线程（默认 HashedWheelTimer）执行，否则使用该执行器来执行\n     */\n    Executor getExecutor();\n}\n\ninterface DelayTaskRegionEnhance extends DelayTaskRegion {\n    void stop();\n\n    void forEach(Consumer<DelayTaskExecutor> consumer);\n\n    void runDelayTask(DelayTaskExecutor delayTaskExecutor);\n}\n\n@Getter\nfinal class DelayIntervalTaskListener implements IntervalTaskListener {\n    final DelayTaskRegionEnhance delayTaskRegion;\n    boolean active = true;\n\n    DelayIntervalTaskListener(DelayTaskRegionEnhance delayTaskRegion) {\n        this.delayTaskRegion = delayTaskRegion;\n    }\n\n    @Override\n    public void onUpdate() {\n        this.delayTaskRegion.forEach(task -> {\n            var executor = task.getExecutor();\n\n            if (Objects.nonNull(executor)) {\n                executor.execute(() -> extractedFlowTaskListener(task));\n            } else {\n                this.extractedFlowTaskListener(task);\n            }\n        });\n    }\n\n    private void extractedFlowTaskListener(DelayTaskExecutor task) {\n        try {\n            // 移除不活跃的延时任务\n            if (!task.isActive()) {\n                this.delayTaskRegion.cancel(task.getTaskId());\n                return;\n            }\n\n            if (task.triggerUpdate()) {\n                task.onUpdate();\n            }\n        } catch (Throwable e) {\n            task.onException(e);\n        }\n    }\n}\n\n@Getter\nclass SimpleDelayTask implements DelayTaskExecutor {\n    static final AtomicLong taskIdCounter = new AtomicLong();\n\n    final String taskId;\n    final TaskListener taskListener;\n    final DelayTaskRegionEnhance delayTaskRegion;\n    final LongAdder timeMillis = new LongAdder();\n    final AtomicBoolean active = new AtomicBoolean(true);\n\n    SimpleDelayTask(TaskListener taskListener, DelayTaskRegionEnhance delayTaskRegion) {\n        this(String.valueOf(taskIdCounter.getAndIncrement()), taskListener, delayTaskRegion);\n    }\n\n    SimpleDelayTask(String taskId, TaskListener taskListener, DelayTaskRegionEnhance delayTaskRegion) {\n        this.taskId = taskId;\n        this.taskListener = taskListener;\n        this.delayTaskRegion = delayTaskRegion;\n    }\n\n    @Override\n    public boolean isActive() {\n        return this.active.get();\n    }\n\n    @Override\n    public void cancel() {\n        if (this.isActive() && this.active.compareAndSet(true, false)) {\n            this.delayTaskRegion.cancel(this.taskId);\n        }\n    }\n\n    @Override\n    public long getMillis() {\n        var sum = this.timeMillis.sum();\n        return sum < 0 ? 0 : sum;\n    }\n\n    @Override\n    public DelayTask plusTimeMillis(long millis) {\n        if (this.isActive()) {\n            this.timeMillis.add(millis);\n        }\n\n        return this;\n    }\n\n    @Override\n    public void onUpdate() {\n\n        this.cancel();\n\n        if (this.taskListener.triggerUpdate()) {\n            this.taskListener.onUpdate();\n        }\n    }\n\n    static final long INTERVAL_MILLIS_CONSUMER = -SimpleDelayTaskRegion.INTERVAL_MILLIS * 2;\n\n    @Override\n    public boolean triggerUpdate() {\n        // 时间 <= 0 时，就可以执行任务了\n        this.plusTimeMillis(INTERVAL_MILLIS_CONSUMER);\n\n        return this.isActive() && this.getMillis() <= 0;\n    }\n\n    @Override\n    public void onException(Throwable e) {\n        this.taskListener.onException(e);\n    }\n\n    @Override\n    public Executor getExecutor() {\n        return this.taskListener.getExecutor();\n    }\n\n    @Override\n    public DelayTask task() {\n        if (this.isActive()) {\n            this.delayTaskRegion.runDelayTask(this);\n        }\n\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return \"SimpleDelayTask{\" +\n                \"taskId='\" + this.taskId + '\\'' +\n                \", active=\" + this.active +\n                \", timeMillis=\" + getMillis() +\n                '}';\n    }\n}\n\nclass SimpleDelayTaskRegion implements DelayTaskRegion, DelayTaskRegionEnhance {\n    static final long INTERVAL_MILLIS = 50;\n\n    final Map<String, DelayTaskExecutor> taskMap = new NonBlockingHashMap<>();\n    final DelayIntervalTaskListener taskListener;\n\n    SimpleDelayTaskRegion() {\n        this.taskListener = new DelayIntervalTaskListener(this);\n        TaskKit.runInterval(this.taskListener, INTERVAL_MILLIS, TimeUnit.MILLISECONDS);\n    }\n\n    @Override\n    public void forEach(Consumer<DelayTaskExecutor> consumer) {\n        this.taskMap.values().forEach(consumer);\n    }\n\n    @Override\n    public void runDelayTask(DelayTaskExecutor delayTaskExecutor) {\n        this.taskMap.put(delayTaskExecutor.getTaskId(), delayTaskExecutor);\n    }\n\n    @Override\n    public Optional<DelayTask> optional(String taskId) {\n        var task = this.taskMap.get(taskId);\n        return Optional.ofNullable(task);\n    }\n\n    @Override\n    public void cancel(String taskId) {\n        var task = this.taskMap.remove(taskId);\n        if (Objects.nonNull(task) && task.isActive()) {\n            task.cancel();\n        }\n    }\n\n    @Override\n    public int count() {\n        return this.taskMap.size();\n    }\n\n    @Override\n    public void stop() {\n        this.taskListener.active = false;\n    }\n\n    @Override\n    public DelayTask of(TaskListener taskListener) {\n        return new SimpleDelayTask(taskListener, this);\n    }\n\n    @Override\n    public DelayTask of(String taskId, TaskListener taskListener) {\n        return new SimpleDelayTask(taskId, taskListener, this);\n    }\n}\n\n@Slf4j\nfinal class DebugDelayTask extends SimpleDelayTask {\n    final LongAdder sumMillis = new LongAdder();\n\n    DebugDelayTask(String taskId, TaskListener taskListener, DelayTaskRegionEnhance delayTaskRegion) {\n        super(taskId, taskListener, delayTaskRegion);\n    }\n\n    DebugDelayTask(TaskListener taskListener, DelayTaskRegionEnhance delayTaskRegion) {\n        super(taskListener, delayTaskRegion);\n    }\n\n    @Override\n    public void onUpdate() {\n        super.onUpdate();\n        log.info(\"剩余任务数量 {}，{}\", this.delayTaskRegion.count(), this);\n    }\n\n    @Override\n    public boolean triggerUpdate() {\n        // 总耗时计算\n        this.sumMillis.add(Math.abs(INTERVAL_MILLIS_CONSUMER));\n\n        return super.triggerUpdate();\n    }\n\n    @Override\n    public String toString() {\n        return \"DebugDelayTask{\" +\n                \"taskId='\" + this.taskId + '\\'' +\n                \", active=\" + this.active +\n                \", timeMillis=\" + this.timeMillis +\n                \", sumMillis=\" + this.sumMillis +\n                \"} \";\n    }\n}\n\nfinal class DebugDelayTaskRegion extends SimpleDelayTaskRegion {\n    @Override\n    public DelayTask of(TaskListener taskListener) {\n        return new DebugDelayTask(taskListener, this);\n    }\n\n    @Override\n    public DelayTask of(String taskId, TaskListener taskListener) {\n        return new DebugDelayTask(taskId, taskListener, this);\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/timer/delay/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - <a href=\"https://iohao.github.io/game/docs/kit/delay_task\">轻量可控的延时任务</a>，任务到达指定时间后会执行、任务可取消、任务可增加或减少延时时间、任务可被覆盖、可设置任务监听回调。\n * <p>\n * 轻量可控的延时任务 - 简介\n * <pre>\n *     我们知道，在 {@link com.iohao.game.common.kit.concurrent.TaskKit} 中，提供了一个任务、时间、延时监听、超时监听 ...等相结合的一个工具模块，通过 runOnce 可以执行一些延时任务；\n *     但有时，我们需要一些可控的延时任务，也就是延时时间可以根据后续的业务来变化，比如增加或减少延时的时间、取消任务 ...等可控的操作。\n * </pre>\n * 轻量可控的延时任务 - 特点\n * <pre>\n *     1. 单一职责原则\n *     2. 任务到达指定时间后会执行\n *     3. 任务可取消\n *     4. 任务可被覆盖\n *     5. 任务可增加、减少延时的时间\n *     6. 可设置任务监听回调\n *     7. 内部使用 Netty HashedWheelTimer，轻松支持百万任务。\n * </pre>\n * for example\n * <pre>{@code\n * public class DelayTaskTest {\n *     @Test\n *     public void runDelayTask() {\n *         // ---------------演示 - 延时任务---------------\n *         // 1 秒后执行延时任务\n *         DelayTaskKit.of(() -> {\n *                     log.info(\"1 秒后执行的延时任务\");\n *                 })\n *                 .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时\n *                 .task(); // 启动任务\n *     }\n *\n *     @Test\n *     public void plusDelayTime() {\n *         // ---------------演示 - 增加延时时间---------------\n *         long timeMillis = System.currentTimeMillis();\n *\n *         DelayTask delayTask = DelayTaskKit.of(() -> {\n *                     long value = System.currentTimeMillis() - timeMillis;\n *                     log.info(\"增加延时时间，最终 {} ms 后，执行延时任务\", value);\n *                     // Assert.assertTrue(value > 1490);\n *                 })\n *                 .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时\n *                 .task(); // 启动任务\n *\n *         delayTask.plusTimeMillis(500); // 增加 0.5 秒的延时\n *\n *         // 最终 1.5 秒后执行延时任务\n *     }\n *\n *     @Test\n *     public void minusDelayTime() {\n *         // ---------------演示 - 减少延时时间---------------\n *         long timeMillis = System.currentTimeMillis();\n *\n *         // 1 秒后执行延时任务\n *         DelayTask delayTask = DelayTaskKit.of(() -> {\n *                     long value = System.currentTimeMillis() - timeMillis;\n *                     log.info(\"减少延时时间，最终 {} ms 后，执行延时任务\", value);\n *                     // Assert.assertTrue(value < 510);\n *                 })\n *                 .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时\n *                 .task(); // 启动任务\n *\n *         delayTask.minusTimeMillis(500); // 减少 0.5 秒的延时时间\n *\n *         // 最终 0.5 秒后执行延时任务\n *     }\n *\n *     @Test\n *     public void coverDelayTask() throws InterruptedException {\n *         // ---------------演示 - 覆盖延时任务---------------\n *\n *         String taskId = \"1\";\n *\n *         DelayTaskKit.of(taskId, () -> log.info(\"执行任务 - 1\"))\n *                 .plusTime(Duration.ofSeconds(2)) // 增加 2 秒的延时\n *                 .task(); // 启动任务\n *\n *         TimeUnit.MILLISECONDS.sleep(500);\n *\n *         long timeMillis = System.currentTimeMillis();\n *\n *         // 因为 taskId 相同，所以会覆盖之前的延时任务\n *         DelayTask delayTask = DelayTaskKit.of(taskId, () -> {\n *                     long value = System.currentTimeMillis() - timeMillis;\n *                     log.info(\"执行任务 - 2，最终 {} ms 后，执行延时任务\", value);\n *                     // Assert.assertTrue(value > 990);\n *                 })\n *                 .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时\n *                 .task(); // 启动任务\n *     }\n *\n *     @Test\n *     public void cancelDelayTask() throws InterruptedException {\n *         // ---------------演示 - 取消延时任务，通过 DelayTask 来取消---------------\n *\n *         DelayTask delayTask = DelayTaskKit.of(() -> {\n *                     log.info(\"取消 - 延时任务\");\n *                 })\n *                 .plusTime(Duration.ofSeconds(2)) // 增加 2 秒的延时\n *                 .task(); // 启动任务\n *\n *         log.info(\"0.5 秒后, 因为满足某个业务条件, 不想执行定时任务了\");\n *         TimeUnit.MILLISECONDS.sleep(500);\n *\n *         delayTask.isActive(); // true\n *         delayTask.cancel(); // 取消任务\n *         delayTask.isActive(); // false\n *\n *         // -----------演示 - 取消延时任务，通过 taskId 来取消-----------\n *\n *         String taskId = \"1\";\n *         // 在创建延时任务时，设置 taskId\n *         DelayTaskKit.of(taskId, () -> log.info(\"通过 taskId 取消 - 延时任务\"))\n *                 .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时\n *                 .task(); // 启动任务\n *\n *         log.info(\"0.5 秒后, 因为满足某个业务条件, 不想执行定时任务了\");\n *         TimeUnit.MILLISECONDS.sleep(500);\n *         DelayTaskKit.cancel(taskId); // 通过 taskId 取消任务\n *     }\n *\n *     @Test\n *     public void optionalDelayTask() {\n *         // ---------------演示 - 查找延时任务---------------\n *\n *         String newTaskId = \"1\";\n *         DelayTaskKit.of(newTaskId, () -> log.info(\"hello DelayTask\"))\n *                 // 2.5 秒后执行延时任务。（这里演示添加延时时间的两个方法）\n *                 .plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时\n *                 .plusTime(Duration.ofMillis(1000)) // 增加 1 秒的延时\n *                 .plusTimeMillis(500) // 增加 0.5 秒的延时\n *                 .task(); // 启动任务\n *\n *         // 在后续的业务中，可以通过 taskId 查找该延时任务\n *         Optional<DelayTask> optionalDelayTask = DelayTaskKit.optional(newTaskId);\n *         if (optionalDelayTask.isPresent()) {\n *             DelayTask delayTask = optionalDelayTask.get();\n *             log.info(\"{}\", delayTask);\n *         }\n *\n *         // 通过 taskId 查找延时任务，存在则执行给定逻辑\n *         DelayTaskKit.ifPresent(newTaskId, delayTask -> {\n *             delayTask.plusTimeMillis(500); // 增加 0.5 秒的延时时间\n *         });\n *     }\n *\n *     @Test\n *     public void customTaskListener() {\n *         // ---------------演示 - 增强 TaskListener ---------------\n *\n *         DelayTaskKit.of(new TaskListener() {\n *                     @Override\n *                     public void onUpdate() {\n *                         log.info(\"1.7 秒后执行的延时任务\");\n *                     }\n *\n *                     @Override\n *                     public boolean triggerUpdate() {\n *                         // 是否触发 onUpdate 监听回调方法\n *                         return TaskListener.super.triggerUpdate();\n *                     }\n *\n *                     @Override\n *                     public Executor getExecutor() {\n *                         // 指定一个执行器来消费当前 onUpdate\n *                         Executors yourExecutors = ...;\n *                         return yourExecutors;\n *                     }\n *\n *                     @Override\n *                     public void onException(Throwable e) {\n *                         // 异常回调\n *                         TaskListener.super.onException(e);\n *                     }\n *                 })\n *                 .plusTime(Duration.ofMillis(1700))\n *                 .task();\n *     }\n * }\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-09-01\n * @since 21.16\n */\npackage com.iohao.game.common.kit.concurrent.timer.delay;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/exception/CommonIllegalArgumentException.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.exception;\n\n/**\n * IllegalArgumentException\n *\n * @author 渔民小镇\n * @date 2024-08-02\n * @since 21.14\n */\npublic class CommonIllegalArgumentException extends IllegalArgumentException {\n    public CommonIllegalArgumentException(String s) {\n        super(s);\n    }\n\n    public CommonIllegalArgumentException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/exception/CommonNullPointerException.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.exception;\n\n/**\n * NullPointerException\n *\n * @author 渔民小镇\n * @date 2024-08-16\n * @since 21.15\n */\npublic class CommonNullPointerException extends NullPointerException {\n    public CommonNullPointerException() {\n    }\n\n    public CommonNullPointerException(String s) {\n        super(s);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/exception/CommonRuntimeException.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.exception;\n\n/**\n * RuntimeException\n *\n * @author 渔民小镇\n * @date 2024-08-02\n * @since 21.14\n */\npublic class CommonRuntimeException extends RuntimeException {\n    public CommonRuntimeException(String message) {\n        super(message);\n    }\n\n    public CommonRuntimeException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/exception/ThrowKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.exception;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * 实验性工具，仅限内部使用\n *\n * @author 渔民小镇\n * @date 2024-08-01\n * @since 21.14\n */\n@UtilityClass\npublic class ThrowKit {\n    public void ofIllegalArgumentException(String msg) throws CommonIllegalArgumentException {\n        throw new CommonIllegalArgumentException(msg);\n    }\n\n    public void ofIllegalArgumentException(String msg, Exception e) throws CommonIllegalArgumentException {\n        throw new CommonIllegalArgumentException(msg, e);\n    }\n\n    public void ofRuntimeException(String msg) throws CommonRuntimeException {\n        throw new CommonRuntimeException(msg);\n    }\n\n    public void ofRuntimeException(Throwable e) throws CommonRuntimeException {\n        throw new CommonRuntimeException(e.getMessage(), e);\n    }\n\n    public void ofNullPointerException(String msg) throws NullPointerException {\n        throw new NullPointerException(msg);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/exception/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - 内置的异常类（实验性工具，仅限内部使用）。\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.kit.exception;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/id/CacheKeyKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.id;\n\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.lang.reflect.Method;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.UnknownHostException;\nimport java.nio.ByteBuffer;\nimport java.security.SecureRandom;\nimport java.util.Enumeration;\nimport java.util.Random;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author 渔民小镇\n * @date 2021-12-25\n */\n@Slf4j\n@UtilityClass\nclass CacheKeyKit {\n\n    private final String ASSIGNED_SEQUENCES = \"com.iohao.assignedSequences\";\n\n    private final AtomicInteger count = new AtomicInteger(0);\n\n    private final long TYPE1 = 0x1000L;\n\n    private final byte VARIANT = (byte) 0x80;\n\n    private final int SEQUENCE_MASK = 0x3FFF;\n\n    private final long NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000L;\n\n    private final long least;\n\n    private final long LOW_MASK = 0xffffffffL;\n    private final long MID_MASK = 0xffff00000000L;\n    private final long HIGH_MASK = 0xfff000000000000L;\n    private final int NODE_SIZE = 8;\n    private final int SHIFT_2 = 16;\n    private final int SHIFT_4 = 32;\n    private final int SHIFT_6 = 48;\n    private final int HUNDRED_NANOS_PER_MILLI = 10000;\n\n    static {\n        byte[] mac = null;\n        try {\n            final InetAddress address = InetAddress.getLocalHost();\n            try {\n                NetworkInterface ni = NetworkInterface.getByInetAddress(address);\n                if (ni != null && !ni.isLoopback() && ni.isUp()) {\n                    final Method method = ni.getClass().getMethod(\"getHardwareAddress\");\n                    mac = (byte[]) method.invoke(ni);\n                }\n\n                if (mac == null) {\n                    final Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();\n                    while (enumeration.hasMoreElements() && mac == null) {\n                        ni = enumeration.nextElement();\n                        if (ni != null && ni.isUp() && !ni.isLoopback()) {\n                            final Method method = ni.getClass().getMethod(\"getHardwareAddress\");\n                            mac = (byte[]) method.invoke(ni);\n                        }\n                    }\n                }\n            } catch (final Exception ex) {\n                log.error(ex.getMessage(), ex);\n            }\n\n            if (mac == null || mac.length == 0) {\n                mac = address.getAddress();\n            }\n        } catch (final UnknownHostException e) {\n            // Ignore exception\n        }\n\n        final Random randomGenerator = new SecureRandom();\n        if (mac == null || mac.length == 0) {\n            mac = new byte[6];\n            randomGenerator.nextBytes(mac);\n        }\n\n        final int length = Math.min(mac.length, 6);\n        final int index = mac.length >= 6 ? mac.length - 6 : 0;\n        final byte[] node = new byte[NODE_SIZE];\n        node[0] = VARIANT;\n        node[1] = 0;\n        for (int i = 2; i < NODE_SIZE; ++i) {\n            node[i] = 0;\n        }\n\n        System.arraycopy(mac, index, node, index + 2, length);\n        final ByteBuffer buf = ByteBuffer.wrap(node);\n        String assigned = ASSIGNED_SEQUENCES;\n        long[] sequences;\n\n        final String[] array = new String[]{\"1\", \"9\", \"9\", \"0\", \"0\", \"4\", \"0\", \"5\"};\n        sequences = new long[array.length];\n        int i = 0;\n        for (final String value : array) {\n            sequences[i] = Long.parseLong(value);\n            ++i;\n        }\n\n        long rand = randomGenerator.nextLong();\n        rand &= SEQUENCE_MASK;\n        boolean duplicate;\n\n        do {\n            duplicate = false;\n            for (final long sequence : sequences) {\n                if (sequence == rand) {\n                    duplicate = true;\n                    break;\n                }\n            }\n            if (duplicate) {\n                rand = (rand + 1) & SEQUENCE_MASK;\n            }\n        } while (duplicate);\n\n        assigned = assigned + ',' + rand;\n        System.setProperty(ASSIGNED_SEQUENCES, assigned);\n\n        least = buf.getLong() | rand << SHIFT_6;\n    }\n\n    /**\n     * 时间生成UUID\n     *\n     * @return UUID\n     */\n    private UUID getTimeBasedUuid() {\n\n        final long time = ((System.currentTimeMillis() * HUNDRED_NANOS_PER_MILLI) +\n                NUM_100NS_INTERVALS_SINCE_UUID_EPOCH) + (count.incrementAndGet() % HUNDRED_NANOS_PER_MILLI);\n        final long timeLow = (time & LOW_MASK) << SHIFT_4;\n        final long timeMid = (time & MID_MASK) >> SHIFT_2;\n        final long timeHi = (time & HIGH_MASK) >> SHIFT_6;\n        final long most = timeLow | timeMid | TYPE1 | timeHi;\n        return new UUID(most, least);\n    }\n\n    /**\n     * 时间生成UUID\n     *\n     * @return UUID str\n     */\n    public String uuid() {\n        return getTimeBasedUuid().toString();\n    }\n\n    public static void main(String[] args) {\n        System.out.println(uuid());\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/id/IdKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.id;\n\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\n/**\n * 便捷的 id 工具\n *\n * @author 渔民小镇\n * @date 2024-02-17\n */\n@UtilityClass\npublic class IdKit {\n    /** 默认实现是 uuid */\n    @Setter\n    StringIdSupplier stringIdSupplier = CacheKeyKit::uuid;\n\n    /**\n     * 获取一个唯一的 string id\n     *\n     * @return string id\n     */\n    public String sid() {\n        return stringIdSupplier.get();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/id/StringIdSupplier.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.id;\n\nimport java.util.function.Supplier;\n\n/**\n * @author 渔民小镇\n * @date 2024-02-17\n */\npublic interface StringIdSupplier extends Supplier<String> {\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/id/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - id 生成\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.kit.id;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/micro/room/MicroRoom.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.micro.room;\n\n/**\n * 微房间\n *\n * @author 渔民小镇\n * @date 2023-07-12\n */\npublic interface MicroRoom {\n    void setId(long id);\n\n    long getId();\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/micro/room/MicroRooms.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.micro.room;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\n\n/**\n * 微房间管理者\n *\n * @author 渔民小镇\n * @date 2023-07-12\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class MicroRooms<Room extends MicroRoom> {\n    @Getter\n    final Map<Long, Room> roomMap = new NonBlockingHashMap<>();\n\n    @Setter\n    Supplier<Room> roomSupplier;\n\n    public boolean contains(long id) {\n        return roomMap.containsKey(id);\n    }\n\n    public Room remove(long id) {\n        return this.roomMap.remove(id);\n    }\n\n    public Room getRoom(long id) {\n        return roomMap.get(id);\n    }\n\n    public Room add(Room room) {\n        long id = room.getId();\n        var anyRegion = roomMap.putIfAbsent(id, room);\n\n        if (Objects.isNull(anyRegion)) {\n            anyRegion = roomMap.get(id);\n        }\n\n        return anyRegion;\n    }\n\n    public Optional<Room> optionalRoom(long id) {\n        return Optional.ofNullable(roomMap.get(id));\n    }\n\n    /**\n     * 得到对应的房间对象\n     * <pre>\n     *     如果房间不存在就创建\n     * </pre>\n     *\n     * @param id roomId\n     * @return 房间对象\n     */\n    public Room ofRoom(long id) {\n\n        Room region = roomMap.get(id);\n\n        if (Objects.isNull(region)) {\n            region = roomSupplier.get();\n            region.setId(id);\n\n            region = add(region);\n        }\n\n        return region;\n    }\n\n    public Stream<Room> stream() {\n        return this.roomMap.values().stream();\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/micro/room/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 -（实验性工具，仅限内部使用）\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.kit.micro.room;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\npackage com.iohao.game.common.kit;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/time/CacheTimeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.time;\n\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport lombok.Getter;\nimport lombok.experimental.UtilityClass;\n\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 日期与时间的缓存工具，当开启缓存后，可减少时间相关对象的创建，但也会损失一些精度；缓存默认是关闭的，默认情况下使用实时数据。\n * 如果对时间要求不需要很精准的，建议启用。\n * <p>\n * 开启时间与日期缓存优化\n * <pre>{@code\n *     // 开启时间日期相关的优化\n *     CacheTimeKit.enableCache();\n * }\n * </pre>\n * for example\n * <pre>{@code\n *     // 获取 LocalDate，【每分钟】更新一次，可有效减少 LocalDate 对象的创建。\n *     LocalDate localDate = CacheTimeKit.nowLocalDate();\n *\n *     // 获取 LocalDateTime，【每秒】更新一次，可有效减少 LocalDateTime 对象的创建。\n *     LocalDateTime localDateTime = CacheTimeKit.nowLocalDateTime();\n *\n *     // 获取 System.currentTimeMillis()，【每秒】更新一次\n *     long currentTimeMillis = CacheTimeKit.currentTimeMillis();\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\n@UtilityClass\npublic final class CacheTimeKit {\n    private volatile LocalTime localTime = LocalTime.now();\n    private volatile LocalDate localDate = LocalDate.now();\n    private volatile LocalDateTime localDateTime = LocalDateTime.now();\n    private volatile long currentTimeMillis = System.currentTimeMillis();\n\n    @Getter\n    private boolean cache;\n\n    /**\n     * get LocalDate\n     *\n     * @return LocalDate\n     */\n    public LocalDate nowLocalDate() {\n        return cache ? localDate : LocalDate.now();\n    }\n\n    /**\n     * get LocalDateTime\n     *\n     * @return LocalDateTime\n     */\n    public LocalDateTime nowLocalDateTime() {\n        return cache ? localDateTime : LocalDateTime.now();\n    }\n\n    /**\n     * get LocalTime\n     *\n     * @return LocalTime\n     */\n    public LocalTime nowLocalTime() {\n        return cache ? localTime : LocalTime.now();\n    }\n\n    /**\n     * get currentTimeMillis\n     *\n     * @return System.currentTimeMillis()\n     */\n    public long currentTimeMillis() {\n        return cache ? currentTimeMillis : System.currentTimeMillis();\n    }\n\n    /**\n     * 开启优化缓存\n     */\n    public void enableCache() {\n        if (!cache) {\n            cache = true;\n\n            TaskKit.runInterval(() -> {\n                // 每秒更新一次当前时间\n                localDateTime = LocalDateTime.now();\n                currentTimeMillis = System.currentTimeMillis();\n            }, 1, TimeUnit.SECONDS);\n\n            TaskKit.runInterval(() -> {\n                // 每分钟更新一次当前时间\n                localDate = LocalDate.now();\n                localTime = LocalTime.now();\n            }, 1, TimeUnit.MINUTES);\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/time/ConfigTimeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.time;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\nimport java.time.ZoneId;\n\n/**\n * 时间日期 - 全局配置\n *\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\n@UtilityClass\npublic final class ConfigTimeKit {\n    @Getter\n    @Setter\n    ZoneId defaultZoneId = ZoneId.systemDefault();\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/time/ExpireTimeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.time;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.time.LocalDate;\n\n/**\n * 日期与时间 - 过期检查工具\n *\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\n@UtilityClass\npublic final class ExpireTimeKit {\n\n    /**\n     * LocalDate EpochDay 过期检测，与当前时间做比较\n     *\n     * @param epochDay LocalDate epochDay\n     * @return true 表示日期已经过期\n     */\n    public boolean expireLocalDate(long epochDay) {\n        var localDate = CacheTimeKit.nowLocalDate();\n        return localDate.toEpochDay() > epochDay;\n    }\n\n    /**\n     * LocalDate 过期检测，与当前时间做比较\n     *\n     * @param localDate localDate\n     * @return true 表示日期已经过期\n     */\n    public boolean expireLocalDate(LocalDate localDate) {\n        return expireLocalDate(localDate.toEpochDay());\n    }\n\n    /**\n     * 过期检测，与当前时间做比较，查看是否过期\n     *\n     * @param timeMillis 需要检测的时间\n     * @return true milliseconds 已经过期\n     */\n    public boolean expireMillis(long timeMillis) {\n        // 时间 - 当前时间\n        return (timeMillis - CacheTimeKit.currentTimeMillis()) <= 0;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/time/FormatTimeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.time;\n\nimport com.iohao.game.common.kit.MoreKit;\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.time.format.DateTimeFormatter;\nimport java.time.temporal.TemporalAccessor;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 日期与时间 - 格式化工具\n *\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\n@UtilityClass\npublic final class FormatTimeKit {\n    private final Map<String, DateTimeFormatter> map = new NonBlockingHashMap<>();\n    private final DateTimeFormatter defaultFormatter = ofPattern(\"yyyy-MM-dd HH:mm:ss\");\n\n    /**\n     * get singleton DateTimeFormatter by pattern\n     *\n     * @param pattern the pattern to use, not null\n     * @return the formatter based on the pattern, not null\n     */\n    public DateTimeFormatter ofPattern(String pattern) {\n        var dateTimeFormatter = map.get(pattern);\n\n        // 无锁化\n        if (Objects.isNull(dateTimeFormatter)) {\n            return MoreKit.putIfAbsent(map, pattern, DateTimeFormatter.ofPattern(pattern));\n        }\n\n        return dateTimeFormatter;\n    }\n\n    /**\n     * 将当前时间格式化为 yyyy-MM-dd HH:mm:ss\n     *\n     * @return yyyy-MM-dd HH:mm:ss\n     */\n    public String format() {\n        return format(CacheTimeKit.nowLocalDateTime());\n    }\n\n    /**\n     * 将指定的 Millis 格式化为 yyyy-MM-dd HH:mm:ss\n     *\n     * @param timeMillis Millis\n     * @return yyyy-MM-dd HH:mm:ss\n     */\n    public String format(long timeMillis) {\n        var localDateTime = ToTimeKit.toLocalDateTime(timeMillis);\n        return format(localDateTime);\n    }\n\n    /**\n     * 将 TemporalAccessor 格式化为 yyyy-MM-dd HH:mm:ss\n     *\n     * @param temporal TemporalAccessor\n     * @return yyyy-MM-dd HH:mm:ss\n     */\n    public String format(TemporalAccessor temporal) {\n        return defaultFormatter.format(temporal);\n    }\n\n    /**\n     * 将 TemporalAccessor 格式化为指定的格式\n     *\n     * @param temporal TemporalAccessor\n     * @param pattern  指定的格式\n     * @return 指定的格式\n     */\n    public String format(TemporalAccessor temporal, String pattern) {\n        return ofPattern(pattern).format(temporal);\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/time/ToTimeKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.time;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.time.Instant;\nimport java.time.LocalDateTime;\n\n/**\n * 日期与时间 - 转换工具\n *\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\n@UtilityClass\npublic final class ToTimeKit {\n\n    /**\n     * 将 LocalDateTime 转为 Millis\n     *\n     * @param localDateTime LocalDateTime\n     * @return Millis\n     */\n    public long toMillis(LocalDateTime localDateTime) {\n        return toInstant(localDateTime).toEpochMilli();\n    }\n\n    /**\n     * 将 LocalDateTime 转为 Second\n     *\n     * @param localDateTime localDateTime\n     * @return Second\n     */\n    public int toSeconds(LocalDateTime localDateTime) {\n        return (int) toInstant(localDateTime).getEpochSecond();\n    }\n\n    /**\n     * 将 Millis 转为 LocalDateTime\n     *\n     * @param timeMillis Millis\n     * @return LocalDateTime\n     */\n    public LocalDateTime toLocalDateTime(long timeMillis) {\n        var instant = Instant.ofEpochMilli(timeMillis);\n        var zonedDateTime = instant.atZone(ConfigTimeKit.defaultZoneId);\n        return zonedDateTime.toLocalDateTime();\n    }\n\n    /**\n     * Converts this localDateTime to an Instant\n     *\n     * @param localDateTime localDateTime\n     * @return Instant\n     */\n    public Instant toInstant(LocalDateTime localDateTime) {\n        return localDateTime\n                .atZone(ConfigTimeKit.defaultZoneId)\n                .toInstant();\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/time/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - 时间与日期相关工具包\n *\n * @author 渔民小镇\n * @date 2024-08-27\n */\npackage com.iohao.game.common.kit.time;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/trace/TraceIdSupplier.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.trace;\n\nimport java.util.function.Supplier;\n\n/**\n * TraceId 生成策略\n *\n * @author 渔民小镇\n * @date 2024-01-21\n */\n@FunctionalInterface\npublic interface TraceIdSupplier extends Supplier<String> {\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/trace/TraceKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.trace;\n\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * Trace 相关工具\n *\n * @author 渔民小镇\n * @date 2023-12-20\n */\n@UtilityClass\npublic class TraceKit {\n    final Map<String, TraceIdSupplier> traceIdSupplierMap = new NonBlockingHashMap<>();\n    TraceIdSupplier defaultTraceIdSupplier = new SimpleTraceIdSupplier();\n    public final String traceName = \"ioGameTraceId\";\n\n    public void setDefaultTraceIdSupplier(TraceIdSupplier traceIdSupplier) {\n        TraceKit.defaultTraceIdSupplier = traceIdSupplier;\n    }\n\n    public void putTraceIdSupplier(String name, TraceIdSupplier traceIdSupplier) {\n        traceIdSupplierMap.putIfAbsent(name, traceIdSupplier);\n    }\n\n    public String newTraceId(String name) {\n        return traceIdSupplierMap.getOrDefault(name, defaultTraceIdSupplier).get();\n    }\n\n    public String newTraceId() {\n        return defaultTraceIdSupplier.get();\n    }\n\n    private final class SimpleTraceIdSupplier implements TraceIdSupplier {\n        final AtomicLong id = new AtomicLong(System.currentTimeMillis());\n\n        @Override\n        public String get() {\n            return Long.toString(id.getAndIncrement());\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/trace/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - Trace 相关工具，相关参考<a href=\"https://iohao.github.io/game/docs/manual/trace\">全链路调用日志跟踪</a>。\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.kit.trace;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/weight/Weight.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.weight;\n\n/**\n * 权重接口\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\npublic interface Weight {\n    /**\n     * 权重值\n     *\n     * @return 权重值\n     */\n    int getWeightVal();\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/weight/WeightKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.common.kit.weight;\n\nimport com.iohao.game.common.kit.RandomKit;\n\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 权重工具\n *\n * @author 渔民小镇\n * @date 2022-01-02\n * @see Weight\n */\npublic class WeightKit {\n    /**\n     * 随机抽取 根据权重\n     * <pre>\n     *      将列表的权重值相加，随机获取该权重值\n     *      在遍历列表，并累积元素的权重值。只要当前元素权重大于这个随机值，就返回元素\n     *      如果没有找到大于随机权重值，那么就返回权重最大的元素\n     * </pre>\n     *\n     * @param weights 权重值列表，如果list不为null or empty,那么返回值一定不为null\n     * @param <T>     T\n     * @return 权重值大的元素的获取几率会比较大\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T extends Weight> T roll(List<? extends Weight> weights) {\n        if (Objects.isNull(weights) || weights.isEmpty()) {\n            return null;\n        }\n\n        int weightTotal = 0;\n        int maxWeightValue = 0;\n        Weight maxWeight = null;\n\n        for (Weight weight : weights) {\n            // 获取最大权重值的元素\n            if (weight.getWeightVal() > maxWeightValue) {\n                maxWeightValue = weight.getWeightVal();\n                maxWeight = weight;\n            }\n            // 权重值累积\n            weightTotal += weight.getWeightVal();\n        }\n\n        // 随机获取该权重值\n        int random = RandomKit.randomInt(weightTotal + 1);\n        weightTotal = 0;\n        for (Weight weight : weights) {\n            // 遍历列表，并累积元素的权重值\n            weightTotal += weight.getWeightVal();\n            // 只要当前元素权重大于这个随机值，就返回元素\n            if (weightTotal > random) {\n                return (T) weight;\n            }\n        }\n\n        // 如果没有找到大于随机权重值，那么就返回权重最大的元素\n        return (T) maxWeight;\n    }\n}\n"
  },
  {
    "path": "common/common-micro-kit/src/main/java/com/iohao/game/common/kit/weight/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - 权重相关工具\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\npackage com.iohao.game.common.kit.weight;"
  },
  {
    "path": "common/common-micro-kit/src/main/java/module-info.java.txt",
    "content": "open module com.iohao.game.common.micro.kit {\n    requires transitive lombok;\n    requires transitive org.slf4j;\n    requires transitive jctools.core;\n\n    requires transitive com.iohao.game.common.kit.other.tool;\n\n    exports com.iohao.game.common.consts;\n    exports com.iohao.game.common.internal;\n\n    exports com.iohao.game.common.kit;\n    exports com.iohao.game.common.kit.attr;\n    exports com.iohao.game.common.kit.micro.room;\n    exports com.iohao.game.common.kit.weight;\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/ByteKitTest.java",
    "content": "package com.iohao.game.common.kit;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n */\npublic class ByteKitTest {\n\n    @Test\n    public void toBytes() {\n        assertLong(Long.MAX_VALUE);\n        assertLong(Long.MIN_VALUE);\n        assertLong(0);\n    }\n\n    private void assertLong(long value) {\n        byte[] bytes = ByteKit.toBytes(value);\n        long result = ByteKit.getLong(bytes);\n        Assert.assertEquals(value, result);\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/TimeKitTest.java",
    "content": "package com.iohao.game.common.kit;\n\nimport com.iohao.game.common.kit.time.CacheTimeKit;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.time.LocalDate;\n\n/**\n * @author 渔民小镇\n * @date 2024-06-20\n */\npublic class TimeKitTest {\n    @Test\n    public void test() {\n        long millis = CacheTimeKit.currentTimeMillis();\n        Assert.assertTrue(millis > 0);\n\n        LocalDate localDate = CacheTimeKit.nowLocalDate();\n        Assert.assertTrue(localDate.isEqual(LocalDate.now()));\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/attr/AttrOptionDynamicTest.java",
    "content": "package com.iohao.game.common.kit.attr;\n\nimport lombok.Getter;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2024-06-05\n */\npublic class AttrOptionDynamicTest {\n\n    final MyAttrOptions myAttrOptions = new MyAttrOptions();\n\n    AttrOption<AttrCat> catAttrOption = AttrOption.valueOf(\"AttrCat\");\n\n    @Test\n    public void ifNull() {\n        Assert.assertNull(myAttrOptions.option(catAttrOption));\n\n        // 如果 catAttrOption 属性为 null，创建 AttrCat 对象，并赋值到属性中\n        myAttrOptions.ifNull(catAttrOption, AttrCat::new);\n        Assert.assertNotNull(myAttrOptions.option(catAttrOption));\n\n        myAttrOptions.option(catAttrOption, null);\n        Assert.assertNull(myAttrOptions.option(catAttrOption));\n\n        AttrCat attrCat = new AttrCat();\n        attrCat.name = \"a\";\n        myAttrOptions.option(catAttrOption, attrCat);\n        myAttrOptions.ifNull(catAttrOption, AttrCat::new);\n        Assert.assertEquals(myAttrOptions.option(catAttrOption).name, attrCat.name);\n    }\n\n    private static class AttrCat {\n        String name;\n    }\n\n    @Getter\n    private static class MyAttrOptions implements AttrOptionDynamic {\n        final AttrOptions options = new AttrOptions();\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/beans/property/PropertyValueObservableTest.java",
    "content": "package com.iohao.game.common.kit.beans.property;\n\nimport lombok.ToString;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author 渔民小镇\n * @date 2024-04-17\n */\n@Slf4j\npublic class PropertyValueObservableTest {\n    AtomicInteger counter = new AtomicInteger(0);\n    OnePropertyChangeListener listener = new OnePropertyChangeListener();\n\n    @Test\n    public void testInt() {\n        counter.set(0);\n\n        var property = new IntegerProperty();\n\n        int value = property.get();\n        property.increment();\n        Assert.assertEquals(property.get(), value + 1);\n        property.decrement();\n        Assert.assertEquals(property.get(), value);\n\n        property.addListener(listener);\n        property.addListener((observable, oldValue, newValue) -> {\n            counter.incrementAndGet();\n            log.info(\"2 - int - oldValue:{}, newValue:{}\", oldValue, newValue);\n        });\n\n        property.set(22);\n        property.increment();\n        Assert.assertEquals(counter.get(), 4);\n\n        System.out.println();\n        property.removeListener(listener);\n        property.decrement();\n        Assert.assertEquals(counter.get(), 5);\n    }\n\n    @Test\n    public void testLong() {\n        counter.set(0);\n\n        var property = new LongProperty();\n\n        long value = property.get();\n        property.increment();\n        Assert.assertEquals(property.get(), value + 1);\n        property.decrement();\n        Assert.assertEquals(property.get(), value);\n\n        property.addListener(listener);\n        property.addListener((observable, oldValue, newValue) -> {\n            counter.incrementAndGet();\n            log.info(\"2 - long - oldValue:{}, newValue:{}\", oldValue, newValue);\n        });\n\n        property.set(22);\n        property.increment();\n        Assert.assertEquals(counter.get(), 4);\n\n        System.out.println();\n        property.removeListener(listener);\n        property.decrement();\n        Assert.assertEquals(counter.get(), 5);\n    }\n\n    @Test\n    public void testString() {\n        counter.set(0);\n\n        var property = new StringProperty();\n\n        property.addListener((observable, oldValue, newValue) -> {\n            counter.incrementAndGet();\n            log.info(\"String - oldValue:{}, newValue:{}, observable:{}\", oldValue, newValue, observable);\n        });\n\n        property.set(\"aaa\");\n        property.set(\"bbb\");\n        Assert.assertEquals(counter.get(), 2);\n    }\n\n    @Test\n    public void testBool() {\n        counter.set(0);\n\n        var property = new BooleanProperty();\n\n        property.addListener((observable, oldValue, newValue) -> {\n            counter.incrementAndGet();\n            log.info(\"Boolean - oldValue:{}, newValue:{}, observable:{}\", oldValue, newValue, observable);\n        });\n\n        property.set(true);\n        property.set(false);\n        Assert.assertEquals(counter.get(), 2);\n    }\n\n    @Test\n    public void testObject() {\n        counter.set(0);\n\n        YourUser user = new YourUser();\n        user.age = 100;\n\n        var property = new ObjectProperty<>(user);\n\n        property.addListener((observable, oldValue, newValue) -> {\n            counter.incrementAndGet();\n            log.info(\"object - oldValue:{}, newValue:{}, observable:{}\", oldValue, newValue, observable);\n        });\n\n        property.set(user);\n\n        YourUser user2 = new YourUser();\n        user2.age = 101;\n        property.set(user2);\n\n        Assert.assertEquals(counter.get(), 1);\n    }\n\n    @ToString\n    static class YourUser {\n        int age;\n    }\n\n    @Test\n    public void remove1() {\n        IntegerProperty property = new IntegerProperty(10);\n\n        property.addListener(new PropertyChangeListener<>() {\n            @Override\n            public void changed(PropertyValueObservable<? extends Number> observable, Number oldValue, Number newValue) {\n                log.info(\"1 - newValue : {}\", newValue);\n\n                if (newValue.intValue() == 9) {\n                    // 移除当前监听器\n                    observable.removeListener(this);\n                }\n            }\n        });\n\n        property.decrement(); // value == 9，并触发监听器\n        property.decrement(); // value == 8，由于监听器已经移除，所以不会触发任何事件。\n        Assert.assertEquals(property.get(), 8);\n    }\n\n    @Test\n    public void remove2() {\n        IntegerProperty property = new IntegerProperty(10);\n        // 监听器移除的示例\n        OnePropertyChangeListener onePropertyChangeListener = new OnePropertyChangeListener();\n        property.addListener(onePropertyChangeListener);\n\n        property.increment(); // value == 11，并触发监听器\n        property.removeListener(onePropertyChangeListener); // 移除监听器\n        property.increment(); // value == 12，，由于监听器已经移除，所以不会触发任何事件。\n\n        Assert.assertEquals(property.get(), 12);\n\n    }\n\n    class OnePropertyChangeListener implements PropertyChangeListener<Number> {\n        @Override\n        public void changed(PropertyValueObservable<? extends Number> observable, Number oldValue, Number newValue) {\n            counter.incrementAndGet();\n            log.info(\"1 - oldValue:{}, newValue:{}, observable:{}\", oldValue, newValue, observable);\n        }\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/collect/ListMultiMapTest.java",
    "content": "package com.iohao.game.common.kit.collect;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-07\n */\npublic class ListMultiMapTest {\n    ListMultiMap<Integer, String> map = ListMultiMap.of();\n\n    @Test\n    public void test() {\n        Assert.assertTrue(map.isEmpty());\n\n        map.put(1, \"a\");\n        map.put(1, \"a\");\n        map.put(1, \"b\");\n        Assert.assertEquals(1, map.size());\n        Assert.assertEquals(3, map.sizeValue());\n\n        List<String> list2 = map.get(2);\n        Assert.assertNull(list2);\n\n        list2 = map.of(2);\n        Assert.assertNotNull(list2);\n        Assert.assertEquals(2, map.size());\n\n        list2.add(\"2 - a\");\n        list2.add(\"2 - a\");\n        Assert.assertEquals(5, map.sizeValue());\n        Assert.assertTrue(map.containsValue(\"a\"));\n        Assert.assertTrue(map.containsValue(\"b\"));\n\n        var collection = map.clearAll(1);\n        Assert.assertTrue(collection.isEmpty());\n        Assert.assertEquals(2, map.size());\n\n        Set<Integer> keySet = this.map.keySet();\n        Assert.assertNotNull(keySet);\n        Assert.assertEquals(2, keySet.size());\n        Assert.assertTrue(keySet.contains(1));\n        Assert.assertTrue(keySet.contains(2));\n        Assert.assertFalse(keySet.contains(3));\n\n        this.map.clear();\n        Assert.assertTrue(this.map.isEmpty());\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/collect/SetMultiMapTest.java",
    "content": "package com.iohao.game.common.kit.collect;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-07\n */\npublic class SetMultiMapTest {\n    SetMultiMap<Integer, String> map = SetMultiMap.of();\n\n    @Test\n    public void test() {\n        Assert.assertTrue(map.isEmpty());\n\n        map.put(1, \"a\");\n        map.put(1, \"a\");\n        map.put(1, \"b\");\n\n        Assert.assertEquals(1, map.size());\n        Assert.assertEquals(2, map.sizeValue());\n\n        Set<String> set2 = map.get(2);\n        Assert.assertNull(set2);\n\n        set2 = map.of(2);\n        Assert.assertNotNull(set2);\n        Assert.assertEquals(2, map.size());\n\n        set2.add(\"2 - a\");\n        set2.add(\"2 - a\");\n        Assert.assertEquals(3, map.sizeValue());\n        Assert.assertTrue(map.containsValue(\"a\"));\n        Assert.assertTrue(map.containsValue(\"b\"));\n\n        var collection = map.clearAll(1);\n        Assert.assertTrue(collection.isEmpty());\n        Assert.assertEquals(2, map.size());\n\n        Set<Integer> keySet = this.map.keySet();\n        Assert.assertNotNull(keySet);\n        Assert.assertEquals(2, keySet.size());\n        Assert.assertTrue(keySet.contains(1));\n        Assert.assertTrue(keySet.contains(2));\n        Assert.assertFalse(keySet.contains(3));\n\n        this.map.clear();\n        Assert.assertTrue(this.map.isEmpty());\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/concurrent/TaskKitTest.java",
    "content": "package com.iohao.game.common.kit.concurrent;\n\nimport com.iohao.game.common.kit.RandomKit;\nimport io.netty.util.Timeout;\nimport io.netty.util.TimerTask;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-02\n */\n@Slf4j\npublic class TaskKitTest {\n    @After\n    public void tearDown() throws Exception {\n        TimeUnit.SECONDS.sleep(3);\n    }\n\n    @Test\n    public void execute() {\n        TaskKit.execute(() -> {\n            // 使用 CacheThreadPool 线程池执行任务\n            log.info(\"CacheThreadPool consumer task\");\n        });\n\n        TaskKit.executeVirtual(() -> {\n            // 使用虚拟线程执行任务\n            log.info(\"Virtual consumer task\");\n        });\n    }\n\n    @Test\n    public void runOnce() {\n\n        // 只执行一次，2 秒后执行\n        TaskKit.runOnce(() -> log.info(\"2 Seconds\"), 2, TimeUnit.SECONDS);\n        // 只执行一次，1 分钟后执行\n        TaskKit.runOnce(() -> log.info(\"1 Minute\"), 1, TimeUnit.MINUTES);\n        // 只执行一次，500 milliseconds 后\n        TaskKit.runOnce(() -> log.info(\"500 delayMilliseconds\"), 500, TimeUnit.MILLISECONDS);\n\n        // 只执行一次，1500 Milliseconds后执行，当 theTriggerUpdate 为 true 时，才执行 onUpdate\n        boolean theTriggerUpdate = RandomKit.randomBoolean();\n        TaskKit.runOnce(new OnceTaskListener() {\n            @Override\n            public void onUpdate() {\n                log.info(\"1500 delayMilliseconds\");\n            }\n\n            @Override\n            public boolean triggerUpdate() {\n                return theTriggerUpdate;\n            }\n\n        }, 1500, TimeUnit.MILLISECONDS);\n    }\n\n    @Test\n    public void runInterval() {\n        // 每 2 秒调用一次\n        TaskKit.runInterval(() -> log.info(\"tick 2 Seconds\"), 2, TimeUnit.SECONDS);\n        // 每 30 分钟调用一次\n        TaskKit.runInterval(() -> log.info(\"tick 30 Minute\"), 30, TimeUnit.MINUTES);\n\n\n        //【示例 - 移除任务】每秒调用一次，当 hp 为 0 时就移除当前 Listener\n        TaskKit.runInterval(new IntervalTaskListener() {\n            int hp = 2;\n\n            @Override\n            public void onUpdate() {\n                hp--;\n                log.info(\"剩余 hp:2-{}\", hp);\n            }\n\n            @Override\n            public boolean isActive() {\n                // 当返回 false 则表示不活跃，会从监听列表中移除当前 Listener\n                return hp != 0;\n            }\n        }, 1, TimeUnit.SECONDS);\n\n        //【示例 - 跳过执行】每秒调用一次，当 triggerUpdate 返回值为 true，即符合条件时才执行 onUpdate 方法\n        TaskKit.runInterval(new IntervalTaskListener() {\n            int hp;\n\n            @Override\n            public void onUpdate() {\n                log.info(\"current hp:{}\", hp);\n            }\n\n            @Override\n            public boolean triggerUpdate() {\n                hp++;\n                // 当返回值为 true 时，会执行 onUpdate 方法\n                return hp % 2 == 0;\n            }\n        }, 1, TimeUnit.SECONDS);\n\n        //【示例 - 指定线程执行器】每秒调用一次\n        // 如果有耗时的任务，比如涉及一些 io 操作的，建议指定执行器来执行当前回调（onUpdate 方法），以避免阻塞其他任务。\n        ExecutorService executorService = TaskKit.getCacheExecutor();\n\n        TaskKit.runInterval(new IntervalTaskListener() {\n            @Override\n            public void onUpdate() {\n                log.info(\"执行耗时的 IO 任务，开始\");\n\n                try {\n                    TimeUnit.SECONDS.sleep(3);\n                } catch (InterruptedException e) {\n                    throw new RuntimeException(e);\n                }\n\n                log.info(\"执行耗时的 IO 任务，结束\");\n            }\n\n            @Override\n            public Executor getExecutor() {\n                // 指定执行器来执行当前回调（onUpdate 方法），以避免阻塞其他任务。\n                return executorService;\n            }\n        }, 1, TimeUnit.SECONDS);\n    }\n\n    @Test\n    public void testException() throws InterruptedException {\n        AtomicBoolean hasEx = new AtomicBoolean(false);\n        TaskKit.runOnce(new OnceTaskListener() {\n            @Override\n            public void onUpdate() {\n                throw new RuntimeException(\"hello exception\");\n            }\n\n            @Override\n            public void onException(Throwable e) {\n                hasEx.set(true);\n            }\n        }, 10, TimeUnit.MILLISECONDS);\n\n        TimeUnit.MILLISECONDS.sleep(200);\n        Assert.assertTrue(hasEx.get());\n    }\n\n    @Test\n    public void example() {\n\n        TaskKit.runOnceSecond(() -> {\n        });\n\n        TaskKit.newTimeout(new TimerTask() {\n            @Override\n            public void run(Timeout timeout) throws Exception {\n\n            }\n        }, 1, TimeUnit.SECONDS);\n\n        TaskKit.execute(() -> {\n        });\n\n        TaskKit.runInterval(() -> {\n\n        }, 1, TimeUnit.SECONDS);\n\n        TaskKit.runIntervalMinute(new IntervalTaskListener() {\n            @Override\n            public void onUpdate() {\n\n            }\n        }, 1);\n    }\n\n    //    @Test\n//    public void concurrent() throws InterruptedException {\n//        LongAdder[] arrays = TaskKit.arrays;\n//        log.info(\"arrays : {}\", Arrays.toString(arrays));\n//\n//        for (int i = 0; i < 10; i++) {\n//            extractedThread(arrays.length);\n//            extractedThread(5);\n//        }\n//\n//        TaskKit.runOnce(() -> {\n//            // print\n//            log.info(\"arrays : {}\", Arrays.toString(arrays));\n//        }, 1, TimeUnit.SECONDS);\n//\n//        TimeUnit.SECONDS.sleep(5);\n//    }\n//\n//    private static void extractedThread(int length) {\n//        new Thread(() -> {\n//            for (int j = 1; j < length; j++) {\n//                var tempValue = j;\n//                TaskKit.runInterval(new IntervalTaskListener() {\n//                    public String getValue() {\n//                        return length + \" - \" + tempValue;\n//                    }\n//\n//                    @Override\n//                    public void onUpdate() {\n//\n//                    }\n//                }, j, TimeUnit.SECONDS);\n//            }\n//        }).start();\n//    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/concurrent/executor/ExecutorRegionKitTest.java",
    "content": "package com.iohao.game.common.kit.concurrent.executor;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\n\n/**\n * @author 渔民小镇\n * @date 2024-01-23\n */\n@Slf4j\npublic class ExecutorRegionKitTest {\n\n    @After\n    public void tearDown() {\n        sleep();\n    }\n\n    @Test\n    public void name() {\n        ExecutorRegion executorRegion = ExecutorRegionKit.getExecutorRegion();\n        ExecutorRegion executorRegion2 = ExecutorRegionKit.createExecutorRegion();\n\n        // ExecutorRegion\n        Assert.assertEquals(executorRegion, ExecutorRegionKit.getExecutorRegion());\n        Assert.assertNotEquals(executorRegion, executorRegion2);\n\n        // global single SimpleThreadExecutor\n        Assert.assertEquals(executorRegion.getSimpleThreadExecutor(0),\n                executorRegion.getSimpleThreadExecutor(0));\n        Assert.assertEquals(executorRegion.getSimpleThreadExecutor(0),\n                ExecutorRegionKit.getSimpleThreadExecutor(0));\n\n        Assert.assertEquals(executorRegion2.getSimpleThreadExecutor(0),\n                executorRegion2.getSimpleThreadExecutor(0));\n        Assert.assertEquals(executorRegion2.getSimpleThreadExecutor(0),\n                ExecutorRegionKit.getSimpleThreadExecutor(0));\n\n        // UserThreadExecutor\n        Assert.assertEquals(executorRegion.getUserThreadExecutor(0)\n                , executorRegion.getUserThreadExecutor(0));\n\n        Assert.assertEquals(executorRegion2.getUserThreadExecutor(0)\n                , executorRegion2.getUserThreadExecutor(0));\n\n        Assert.assertNotEquals(executorRegion.getUserThreadExecutor(0)\n                , executorRegion2.getUserThreadExecutor(0));\n    }\n\n    @Test\n    public void userThreadExecutor() {\n        long userId = 1;\n\n        ExecutorRegion executorRegion = ExecutorRegionKit.getExecutorRegion();\n        ThreadExecutor userThreadExecutor = executorRegion.getUserThreadExecutor(userId);\n\n        extracted(userThreadExecutor);\n    }\n\n    private void extracted(ThreadExecutor threadExecutor) {\n        Amount amount = new Amount();\n\n        int loop = 100_000;\n        for (int i = 0; i < loop; i++) {\n            threadExecutor.execute(amount::inc);\n        }\n\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        threadExecutor.execute(() -> {\n            log.info(\"end - amount : {}\", amount);\n            countDownLatch.countDown();\n        });\n\n        try {\n            countDownLatch.await();\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n\n        Assert.assertEquals(amount.num, loop);\n        log.info(\"threadExecutor : {}\", threadExecutor);\n    }\n\n    @Test\n    public void userVirtualThreadExecutor() {\n        long userId = 1;\n\n        ExecutorRegion executorRegion = ExecutorRegionKit.getExecutorRegion();\n        ThreadExecutor userVirtualThreadExecutor = executorRegion.getUserVirtualThreadExecutor(userId);\n\n        userVirtualThreadExecutor.execute(() -> {\n            // print 1\n            log.info(\"userVirtualThreadExecutor : 1\");\n        });\n\n        userVirtualThreadExecutor.execute(() -> {\n            // print 2\n            log.info(\"userVirtualThreadExecutor : 2\");\n        });\n    }\n\n    @Test\n    public void simpleThreadExecutor() {\n        long userId = 1;\n\n        ThreadExecutor simpleThreadExecutor = ExecutorRegionKit.getSimpleThreadExecutor(userId);\n        extracted(simpleThreadExecutor);\n    }\n\n    void sleep() {\n        try {\n            TimeUnit.MILLISECONDS.sleep(100);\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    static final class Amount {\n        int num;\n\n        void inc() {\n            this.num++;\n        }\n\n        @Override\n        public String toString() {\n            return \"Amount{\" +\n                    \"num=\" + num +\n                    '}';\n        }\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/concurrent/timer/delay/DelayTaskTest.java",
    "content": "package com.iohao.game.common.kit.concurrent.timer.delay;\n\nimport com.iohao.game.common.kit.RandomKit;\nimport com.iohao.game.common.kit.concurrent.TaskListener;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.time.Duration;\nimport java.util.Optional;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2024-09-01\n */\n@Slf4j\npublic class DelayTaskTest {\n    DelayTaskRegion delayTaskRegion;\n\n    @Before\n    public void setUp() {\n        DelayTaskKit.setDelayTaskRegion(new DebugDelayTaskRegion());\n        delayTaskRegion = DelayTaskKit.delayTaskRegion;\n    }\n\n    @After\n    public void tearDown() throws Exception {\n        TimeUnit.SECONDS.sleep(3);\n        log.info(\"--------剩余任务数量 {}\", delayTaskRegion.count());\n    }\n\n    @Test\n    public void runDelayTask() {\n        log.info(\"演示 - 延时任务\");\n\n        long timeMillis = System.currentTimeMillis();\n        // 1 秒后执行延时任务\n        DelayTaskKit.of(() -> {\n                    log.info(\"1 秒后执行的延时任务\");\n                    long value = System.currentTimeMillis() - timeMillis;\n                    Assert.assertTrue(value > 990);\n                })\n                // N 秒后触发\n                .plusTime(Duration.ofSeconds(1))\n                // 启动任务\n                .task();\n    }\n\n    @Test\n    public void plusDelayTime() {\n        // ---------------增加 - 延时时间---------------\n        log.info(\"演示 - 增加延时时间\");\n\n        long timeMillis = System.currentTimeMillis();\n        // 1 秒后执行延时任务\n        DelayTask delayTask = DelayTaskKit.of(() -> {\n                    long value = System.currentTimeMillis() - timeMillis;\n                    log.info(\"增加延时时间，最终 {} ms 后，执行延时任务\", value);\n                    Assert.assertTrue(value > 1490);\n                })\n                // N 秒后触发\n                .plusTime(Duration.ofSeconds(1))\n                // 启动任务\n                .task();\n\n        // 增加 0.5 秒的延时\n        delayTask.plusTimeMillis(500);\n        log.info(\"{}\", delayTask);\n        // 最终 1.5 秒后执行延时任务\n    }\n\n    @Test\n    public void minusDelayTime() {\n        // ---------------减少 - 延时时间---------------\n        log.info(\"演示 - 减少延时时间\");\n\n        long timeMillis = System.currentTimeMillis();\n        // 1 秒后执行延时任务\n        DelayTask delayTask = DelayTaskKit.of(() -> {\n                    long value = System.currentTimeMillis() - timeMillis;\n                    log.info(\"减少延时时间，最终 {} ms 后，执行延时任务\", value);\n\n                    Assert.assertTrue(value < 510);\n                })\n                // N 秒后触发\n                .plusTime(Duration.ofSeconds(1))\n                // 启动任务\n                .task();\n\n        // 减少 0.5 秒的延时时间\n        delayTask.minusTime(Duration.ofMillis(100))\n                .plusTimeMillis(-400)\n        ;\n\n        log.info(\"{}\", delayTask);\n        // 最终 0.5 秒后执行延时任务\n    }\n\n    @Test\n    public void coverDelayTask() throws InterruptedException {\n        log.info(\"演示 - 覆盖延时任务\");\n\n        String taskId = \"1\";\n\n        DelayTaskKit.of(taskId, () -> log.info(\"执行任务 - 1\"))\n                // N 秒后触发\n                .plusTime(Duration.ofSeconds(2))\n                // 启动任务\n                .task();\n\n        TimeUnit.MILLISECONDS.sleep(500);\n\n        long timeMillis = System.currentTimeMillis();\n\n        // 因为 taskId 相同，所以会覆盖之前的延时任务\n        DelayTask delayTask = DelayTaskKit.of(taskId, () -> {\n                    long value = System.currentTimeMillis() - timeMillis;\n\n                    log.info(\"执行任务 - 2，最终 {} ms 后，执行延时任务\", value);\n\n                    Assert.assertTrue(value > 990);\n                })\n                // N 秒后触发\n                .plusTime(Duration.ofSeconds(1))\n                // 启动任务\n                .task();\n\n        log.info(\"{}\", delayTask);\n    }\n\n    @Test\n    public void cancelDelayTask() throws InterruptedException {\n        // -----------取消 - 延时任务；通过 DelayTask 来取消-----------\n        log.info(\"演示 - 取消延时任务\");\n\n        DelayTask delayTask = DelayTaskKit.of(() -> {\n                    log.info(\"取消 - 延时任务\");\n                })\n                // N 秒后触发\n                .plusTime(Duration.ofSeconds(2))\n                // 启动任务\n                .task();\n\n        Assert.assertEquals(1, delayTaskRegion.count());\n\n        log.info(\"0.5 秒后, 因为满足某个业务条件, 不想执行定时任务了\");\n        TimeUnit.MILLISECONDS.sleep(500);\n        // 取消任务\n        delayTask.cancel();\n\n        Assert.assertFalse(delayTask.isActive());\n        Assert.assertEquals(0, delayTaskRegion.count());\n\n        // -----------取消 - 延时任务；通过 taskId 来取消-----------\n\n        log.info(\"演示 - 通过 taskId 取消延时任务\");\n\n        String taskId = \"1\";\n        // 在创建延时任务时，设置 taskId\n        DelayTaskKit.of(taskId, () -> log.info(\"通过 taskId 取消 - 延时任务\"))\n                // N 秒后触发\n                .plusTime(Duration.ofSeconds(1))\n                // 启动任务\n                .task();\n\n        Assert.assertEquals(1, delayTaskRegion.count());\n\n        log.info(\"0.5 秒后, 因为满足某个业务条件, 不想执行定时任务了\");\n        TimeUnit.MILLISECONDS.sleep(500);\n        // 通过 taskId 取消任务\n        DelayTaskKit.cancel(taskId);\n\n        Assert.assertEquals(0, delayTaskRegion.count());\n    }\n\n    @Test\n    public void optionalDelayTask() {\n        String newTaskId = \"1\";\n        DelayTaskKit.of(newTaskId, () -> log.info(\"hello DelayTask\"))\n                // 2.5 秒后触发\n                .plusTime(Duration.ofSeconds(2))\n                .plusTimeMillis(500)\n                // 启动任务\n                .task();\n\n        // 在后续的业务中，可以通过 taskId 查找该延时任务\n        Optional<DelayTask> optionalDelayTask = DelayTaskKit.optional(newTaskId);\n        if (optionalDelayTask.isPresent()) {\n            DelayTask delayTask = optionalDelayTask.get();\n            log.info(\"{}\", delayTask);\n        }\n\n        // 通过 taskId 查找延时任务，存在则执行给定逻辑\n        DelayTaskKit.ifPresent(newTaskId, delayTask -> {\n            delayTask.plusTimeMillis(500); // 增加 0.5 秒的延时时间\n        });\n    }\n\n    @Test\n    public void customTaskListener() {\n        // minus\n        // ---------------演示 - 增强 TaskListener ---------------\n\n        DelayTaskKit.of(new TaskListener() {\n                    @Override\n                    public void onUpdate() {\n                        log.info(\"1.7 秒后执行的延时任务\");\n                    }\n\n                    @Override\n                    public boolean triggerUpdate() {\n                        // 是否触发 onUpdate 监听回调方法\n                        return TaskListener.super.triggerUpdate();\n                    }\n\n                    @Override\n                    public Executor getExecutor() {\n                        // 指定一个执行器来消费当前 onUpdate\n                        return TaskListener.super.getExecutor();\n                    }\n\n                    @Override\n                    public void onException(Throwable e) {\n                        // 异常回调\n                        TaskListener.super.onException(e);\n                    }\n                })\n                .plusTime(Duration.ofMillis(1700))\n                .task();\n    }\n\n    @Test\n    public void more() {\n        DelayTask delayTask = DelayTaskKit.of(new ShootTaskListener(\"敌人\"))\n                // 1.5 秒后触发。（这里演示添加延时时间的两个方法）\n                .plusTime(Duration.ofSeconds(1))\n                .plusTimeMillis(500)\n                // 启动任务\n                .task();\n\n        // 当为 true 时，冷却减 0.5 秒，并且幸运加成\n        if (RandomKit.randomBoolean()) {\n            delayTask.minusTime(Duration.ofMillis(500));\n            // 得到监听器，并设置幸运加成\n            ShootTaskListener shootTaskListener = delayTask.getTaskListener();\n            shootTaskListener.setLuck(true);\n        }\n    }\n\n    @Slf4j\n    @Setter\n    static final class ShootTaskListener implements TaskListener {\n        final String targetEntity;\n        /** 幸运加成 */\n        boolean luck;\n        int attack = 10;\n\n        public ShootTaskListener(String targetEntity) {\n            this.targetEntity = targetEntity;\n        }\n\n        @Override\n        public void onUpdate() {\n            int value = luck ? attack * 2 : attack;\n            log.info(\"向【{}】开炮，造成 {} 伤害\", targetEntity, value);\n        }\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/time/CacheTimeKitTest.java",
    "content": "package com.iohao.game.common.kit.time;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\n\nimport static org.junit.Assert.*;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\npublic class CacheTimeKitTest {\n\n    @Test\n    public void nowLocalDate() {\n        Assert.assertTrue(CacheTimeKit.nowLocalDate().isEqual(LocalDate.now()));\n        Assert.assertTrue(CacheTimeKit.nowLocalDateTime().isBefore(LocalDateTime.now()));\n\n        var result = System.currentTimeMillis() - CacheTimeKit.currentTimeMillis();\n        Assert.assertTrue(Math.abs(result) < 10);\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/time/ExpireTimeKitTest.java",
    "content": "package com.iohao.game.common.kit.time;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.time.LocalDate;\n\nimport static org.junit.Assert.*;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\npublic class ExpireTimeKitTest {\n\n    @Test\n    public void expireLocalDate() {\n        LocalDate localDate = CacheTimeKit.nowLocalDate()\n                .minusDays(1);\n\n        Assert.assertTrue(ExpireTimeKit.expireLocalDate(localDate));\n\n        long timeMillis = System.currentTimeMillis() - 1;\n        Assert.assertTrue(ExpireTimeKit.expireMillis(timeMillis));\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/time/FormatTimeKitTest.java",
    "content": "package com.iohao.game.common.kit.time;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.time.LocalDate;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-27\n * @since 21.16\n */\n@Slf4j\npublic class FormatTimeKitTest {\n\n    @Test\n    public void format() {\n        String pattern = \"yyyy-MM-dd\";\n        Assert.assertEquals(FormatTimeKit.ofPattern(pattern), FormatTimeKit.ofPattern(pattern));\n\n        LocalDate localDate = CacheTimeKit.nowLocalDate();\n        Assert.assertEquals(FormatTimeKit.format(localDate, pattern), FormatTimeKit.format(localDate, pattern));\n\n        Assert.assertNotNull(FormatTimeKit.format(CacheTimeKit.nowLocalDateTime()));\n        Assert.assertNotNull(FormatTimeKit.format());\n    }\n\n    @Test\n    public void name() {\n        LocalDate localDate = CacheTimeKit.nowLocalDate();\n        long epochDay = localDate.toEpochDay();\n        log.info(\"epochDay : {}\", epochDay);\n        log.info(\"epochDay : {}\", localDate.plusDays(3).toEpochDay());\n\n\n        String format = FormatTimeKit.format(System.currentTimeMillis());\n        log.info(\"format : {}\", format);\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/java/com/iohao/game/common/kit/trace/TraceKitTest.java",
    "content": "package com.iohao.game.common.kit.trace;\n\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.slf4j.MDC;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.*;\nimport java.util.function.Consumer;\n\n/**\n * @author 渔民小镇\n * @date 2024-01-21\n */\n@Slf4j\npublic class TraceKitTest {\n    static final ExecutorService executorService = Executors.newCachedThreadPool();\n    final int internalLoop = 10000;\n    final int mainLoop = 10;\n    final CountDownLatch countDownLatch = new CountDownLatch(mainLoop);\n    final Map<String, String> map = new ConcurrentHashMap<>();\n\n    @After\n    public void tearDown() {\n        executorService.shutdown();\n\n        sleep(22);\n    }\n\n    @Test\n    public void newTraceId() {\n        Assert.assertEquals(map.size(), mainLoop * internalLoop);\n    }\n\n    @Test\n    public void newTraceIdUUID() {\n        TraceKit.setDefaultTraceIdSupplier(() -> UUID.randomUUID().toString());\n\n        newTraceIdProcess();\n        Assert.assertEquals(map.size(), mainLoop * internalLoop);\n    }\n\n    @Test\n    public void newTraceIdTemp() {\n\n        TraceKit.setDefaultTraceIdSupplier(new TraceIdSupplier() {\n            int i;\n\n            @Override\n            public String get() {\n                i++;\n                return Integer.toString(i);\n            }\n        });\n\n        newTraceIdProcess();\n\n        Assert.assertNotEquals(map.size(), mainLoop * internalLoop);\n    }\n\n    void newTraceIdProcess() {\n\n        for (int i = 0; i < mainLoop; i++) {\n            loopTraceId(data -> {\n                for (String datum : data) {\n                    map.put(datum, datum);\n                }\n            });\n        }\n\n        await();\n\n        System.out.println(map.size());\n        System.out.println(\"------- id show -------\");\n        int i = 0;\n        for (String s : map.keySet()) {\n            if (i++ > 1) {\n                break;\n            }\n\n            System.out.println(s);\n        }\n    }\n\n    void await() {\n        try {\n            countDownLatch.await();\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    void loopTraceId(Consumer<String[]> consumer) {\n\n        executorService.execute(() -> {\n            int len = internalLoop;\n            String[] data = new String[len];\n            for (int i = 0; i < len; i++) {\n                data[i] = TraceKit.newTraceId();\n            }\n\n            consumer.accept(data);\n\n            countDownLatch.countDown();\n        });\n    }\n\n    @Test\n    public void name() {\n        String traceIdName = \"uuid\";\n\n        String id1 = TraceKit.newTraceId(traceIdName);\n        Assert.assertNotNull(id1);\n        log.info(\"traceId : {}\", id1);\n\n        TraceKit.putTraceIdSupplier(traceIdName, () -> UUID.randomUUID().toString());\n        String id2 = TraceKit.newTraceId(traceIdName);\n        Assert.assertNotNull(id2);\n        log.info(\"traceId : {}\", id2);\n    }\n\n    @Test\n    public void testMDC() {\n\n        long userId = 1;\n        ExecutorRegion executorRegion = ExecutorRegionKit.getExecutorRegion();\n        executorRegion.getUserThreadExecutor(userId).execute(() -> {\n            MDC.put(TraceKit.traceName, \"user thread\");\n            extractedVirtual(userId);\n            MDC.clear();\n        });\n\n        sleep(55);\n    }\n\n    private void extractedVirtual(long userId) {\n        ExecutorRegion executorRegion = ExecutorRegionKit.getExecutorRegion();\n        ThreadExecutor userVirtualThreadExecutor = executorRegion.getUserVirtualThreadExecutor(userId);\n\n        log.info(\"0-1 : {}\", MDC.get(TraceKit.traceName));\n        Assert.assertEquals(\"user thread\", MDC.get(TraceKit.traceName));\n\n        extracted(userVirtualThreadExecutor, \"3-1\", () -> {\n            log.info(\"3-1 : {}\", MDC.get(TraceKit.traceName));\n\n            Assert.assertEquals(\"3-1\", MDC.get(TraceKit.traceName));\n        });\n\n        extracted(userVirtualThreadExecutor, \"4-1\", () -> {\n            log.info(\"4-1 : {}\", MDC.get(TraceKit.traceName));\n\n            Assert.assertEquals(\"4-1\", MDC.get(TraceKit.traceName));\n        });\n\n        sleep(50);\n\n        log.info(\"0-2 : {}\", MDC.get(TraceKit.traceName));\n        Assert.assertEquals(\"user thread\", MDC.get(TraceKit.traceName));\n\n        sleep(10);\n\n    }\n\n    private void extracted(ThreadExecutor userVirtualThreadExecutor, String traceId, Runnable command) {\n        userVirtualThreadExecutor.execute(() -> {\n            try {\n                log.info(\"1-1 : {} - {}\", MDC.get(TraceKit.traceName), traceId);\n                Assert.assertNull(MDC.get(TraceKit.traceName));\n\n                MDC.put(TraceKit.traceName, traceId);\n\n                command.run();\n\n                log.info(\"1-2 : {} - {}\", MDC.get(TraceKit.traceName), traceId);\n                Assert.assertEquals(MDC.get(TraceKit.traceName), traceId);\n            } finally {\n                MDC.clear();\n            }\n        });\n    }\n\n    void sleep(int milliseconds) {\n        try {\n            TimeUnit.MILLISECONDS.sleep(milliseconds);\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n}"
  },
  {
    "path": "common/common-micro-kit/src/test/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration debug=\"false\">\n    <!-- 关闭 logback 启动时打印的无效日志 -->\n    <statusListener class=\"ch.qos.logback.core.status.NopStatusListener\"/>\n    <!-- 日志级别 -->\n    <property name=\"log.root.level\" value=\"INFO\"/>\n    <!-- 其他日志级别 -->\n    <property name=\"log.other.level\" value=\"DEBUG\"/>\n    <!-- 模块名称， 影响日志配置名，日志文件名 -->\n    <property name=\"log.moduleName\" value=\"game\"/>\n    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其；否则，在当前目录下创建名为logs目录做日志存放的目录 -->\n    <property name=\"log.base\" value=\"${log.dir:-logs}/${log.moduleName}\"/>\n    <property name=\"log.max.size\" value=\"100MB\"/> <!-- 日志文件大小,超过这个大小将被压缩 -->\n\n    <!-- 彩色日志 -->\n    <property name=\"log.pattern\"\n              value=\"%d{HH:mm:ss.SSS} %green([%thread]) %highlight(%-5level) %cyan(%logger{5}).%M\\(%F:%L\\) %m%n\"/>\n    <!--控制台输出 -->\n    <appender name=\"stdout\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <!--格式化输出：%d表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度%msg：日志消息，%n是换行符-->\n            <!--            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %highlight(%msg) %n</pattern>-->\n            <pattern>${log.pattern}</pattern>\n            <charset>utf8</charset>\n        </encoder>\n    </appender>\n\n    <!-- 用来保存输出所有级别的日志 -->\n    <appender name=\"file.all\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <File>${log.base}/${log.moduleName}.log</File><!-- 设置日志不超过${log.max.size}时的保存路径，注意如果\n            是web项目会保存到Tomcat的bin目录 下 -->\n        <!-- 滚动记录文件，先将日志记录到指定文件，当符合某个条件时，将日志记录到其他文件。 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <FileNamePattern>${log.base}/archive/${log.moduleName}_all_%d{yyyy-MM-dd}.%i.log.zip</FileNamePattern>\n            <!-- 文件输出日志 (文件大小策略进行文件输出，超过指定大小对文件备份) -->\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>${log.max.size}</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n        </rollingPolicy>\n        <!-- 日志输出的文件的格式 -->\n        <layout class=\"ch.qos.logback.classic.PatternLayout\">\n            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{80}.%method:%L -%msg%n</pattern>\n        </layout>\n    </appender>\n\n    <!-- 这也是用来保存输出所有级别的日志 -->\n    <appender name=\"file.all.other\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <File>${log.base}/${log.moduleName}_other.log</File>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <FileNamePattern>${log.base}/archive/${log.moduleName}_other_%d{yyyy-MM-dd}.%i.log.zip\n            </FileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>${log.max.size}</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n        </rollingPolicy>\n        <layout class=\"ch.qos.logback.classic.PatternLayout\">\n            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{56}.%method:%L -%msg%n</pattern>\n        </layout>\n    </appender>\n\n    <!-- 只用保存输出error级别的日志 -->\n    <appender name=\"file.error\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <File>${log.base}/${log.moduleName}_err.log</File>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <FileNamePattern>${log.base}/archive/${log.moduleName}_err_%d{yyyy-MM-dd}.%i.log.zip\n            </FileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>${log.max.size}</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n        </rollingPolicy>\n        <layout class=\"ch.qos.logback.classic.PatternLayout\">\n            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{56}.%method:%L - %msg%n</pattern>\n        </layout>\n        <!-- 下面为配置只输出error级别的日志 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>ERROR</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->\n    <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->\n    <!-- 添加附加的appender,最多只能添加一个 -->\n    <appender name=\"file.async\" class=\"ch.qos.logback.classic.AsyncAppender\">\n        <discardingThreshold>0</discardingThreshold>\n        <queueSize>256</queueSize>\n        <includeCallerData>true</includeCallerData>\n        <appender-ref ref=\"file.all\"/>\n    </appender>\n\n    　　<!-- 使用异步来记录其他信息-->\n    <appender name=\"file.async.other\" class=\"ch.qos.logback.classic.AsyncAppender\">\n        <discardingThreshold>0</discardingThreshold>\n        <queueSize>256</queueSize>\n        <includeCallerData>true</includeCallerData>\n        <appender-ref ref=\"file.all.other\"/>\n    </appender>\n\n    <!-- 为某个包下的所有类的指定Appender 这里也可以指定类名称例如：com.aa.bb.ClassName -->\n    <logger name=\"com.iohao.game\" additivity=\"false\">\n        <level value=\"${log.root.level}\"/>\n        <appender-ref ref=\"stdout\"/>\n        <appender-ref ref=\"file.async\"/><!-- 即com.iohao.game包下级别为 ${log.root.level}的才会使用file.async来打印 -->\n        <appender-ref ref=\"file.error\"/>\n    </logger>\n\n    <!-- root将级别为${log.root.level}及大于${log.root.level}的日志信息交给已经配置好的名为“Console”的appender处理，“Console”appender将信息打印到Console,其它同理 -->\n    <root level=\"${log.root.level}\">\n        <appender-ref ref=\"stdout\"/> <!--  标识这个appender将会添加到这个logger -->\n        <appender-ref ref=\"file.async.other\"/>\n        <appender-ref ref=\"file.error\"/>\n    </root>\n</configuration>\n"
  },
  {
    "path": "common/common-validation/README.md",
    "content": "# 背景：\n游戏逻辑服务需要与已有的业务服务(`SpringBoot`)进行通信，因此游戏逻辑服务也使用了`SpringBoot`框架，同时也依赖了`spring-cloud-starter-stream-rabbit`，\n问题点在于`spring-cloud-starter-stream-rabbit` 依赖 `javax.validation`,而框架中依赖`jakarta.validation`,实测过程中二者不能兼容，最直接的办法是替换框架中的`jakarta.validation`依赖，\n但是该办法比较粗暴，违背原作者的意图，因此单独抽出一个模块`common-validation`；\n\n# 解决方案：\nJava、SpringBoot中均有SPI的实现，SPI的思路能比较好的解决该问题，因此其中定义了`Validator`接口，用于抽象`javax`和`jakarta`中的`Validator`；实际项目中按需加载。\n同时对框架中的`ValidatorKit`类进行调整\n\n# 使用方法：\n\n## 依赖jakarta.validation的使用\n1. 在游戏逻辑服务的`pom.xml`中添加依赖：\n``` xml\n<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->\n<dependency>\n  <groupId>org.hibernate.validator</groupId>\n  <artifactId>hibernate-validator</artifactId>\n  <version>7.0.4.Final</version>\n</dependency>\n\n\n<!-- https://mvnrepository.com/artifact/org.glassfish/jakarta.el -->\n<!-- EL实现。在Java SE环境中，您必须将实现作为依赖项添加到POM文件中-->\n<dependency>\n  <groupId>org.glassfish</groupId>\n  <artifactId>jakarta.el</artifactId>\n  <version>4.0.2</version>\n</dependency>\n\n<!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->\n<!-- 验证器Maven依赖项 -->\n<dependency>\n  <groupId>jakarta.validation</groupId>\n  <artifactId>jakarta.validation-api</artifactId>\n  <version>3.0.2</version>\n</dependency>\n```\n\n2. 在 `resources` 目录下新建文件 `META-INF/ioGame/com.iohao.game.common.validation.Validator` 并写入内容：\n ```\n com.iohao.game.common.validation.support.JakartaValidator\n ```\n\n或者使用注解：\n``` java\n@EnableValidation(\"com.iohao.game.common.validation.support.JakartaValidator\")\n@SpringBootApplication\npublic class JakartaServerApplication {\n......\n}\n```\n\n> 依赖jakarta.validation时，该步骤为非必须操作\n\n3. 对应实体类中的依赖更改为`jakarta.validation` 相关\n``` java\nimport jakarta.validation.constraints.Pattern;\n/**\n * 登录请求数据\n */\n@Data\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class LoginReq implements Serializable {\n\n    // 用户名\n    @Pattern(regexp = \"^[a-z|A-Z|0-9]{10,20}$\", message = \"用户名长度不正确\")\n    String username;\n\n    // 登录密码\n    String password;\n}\n```\n\n## 依赖javax.validation的使用\n1. 在游戏逻辑服务的`pom.xml`中添加依赖：\n\n```xml\n<!--javax.validation -->\n<dependency>\n    <groupId>org.hibernate</groupId>\n    <artifactId>hibernate-validator</artifactId>\n    <version>5.3.6.Final</version>\n</dependency>\n<dependency>\n<groupId>javax.validation</groupId>\n<artifactId>validation-api</artifactId>\n</dependency>\n<dependency>\n<groupId>javax.el</groupId>\n<artifactId>javax.el-api</artifactId>\n<version>3.0.0</version>\n</dependency>\n<dependency>\n<groupId>org.glassfish.web</groupId>\n<artifactId>javax.el</artifactId>\n<version>2.2.4</version>\n</dependency>\n```\n\n2. 在 `resources` 目录下新建文件 `META-INF/ioGame/com.iohao.game.common.validation.Validator` 并写入内容：\n ```\n com.iohao.game.common.validation.support.JavaxValidator\n ```\n或者使用注解：\n``` java\n@EnableValidation(\"com.iohao.game.common.validation.support.JavaxValidator\")\n@SpringBootApplication\npublic class JavaXServerApplication {\n......\n}\n```\n> 依赖javax.validation时，该步骤为必须操作\n\n3. 对应实体类中的依赖更改为`javax.validation` 相关\n``` java\nimport javax.validation.constraints.Pattern;\n/**\n * 登录请求数据\n */\n@Data\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class LoginReq implements Serializable {\n\n    // 用户名\n    @Pattern(regexp = \"^[a-z|A-Z|0-9]{10,20}$\", message = \"用户名长度不正确\")\n    String username;\n\n    // 登录密码\n    String password;\n}\n```\n\n\n\n# pom 设置\n\n如果使用 javax 的验证，需要在 pom 中加入如下代码；\n\n> 使用默认的 jakarta 则不需要在 pom 加入下面这段配置；\n\n```xml\n<plugin>\n    <groupId>org.apache.maven.plugins</groupId>\n    <artifactId>maven-compiler-plugin</artifactId>\n    <version>3.8.1</version>\n    <configuration>\n        <compilerVersion>${java.version}</compilerVersion>\n        <source>${java.version}</source>\n        <target>${java.version}</target>\n        <!-- maven 3.6.2及之后加上编译参数，可以让我们在运行期获取方法参数名称。 -->\n        <parameters>true</parameters>\n        <skip>true</skip>\n        <!-- JDK9+ with module-info.java -->\n        <annotationProcessorPaths>\n            <!-- 实体映射工具 -->\n            <path>\n                <groupId>org.mapstruct</groupId>\n                <artifactId>mapstruct-processor</artifactId>\n                <version>${org.mapstruct.version}</version>\n            </path>\n\n            <!-- lombok 消除冗长的 Java 代码 -->\n            <path>\n                <groupId>org.projectlombok</groupId>\n                <artifactId>lombok</artifactId>\n                <version>${lombok.version}</version>\n            </path>\n            <!-- additional annotation processor required as of Lombok 1.18.16 -->\n            <!-- mapStruct 支持 lombok -->\n            <path>\n                <groupId>org.projectlombok</groupId>\n                <artifactId>lombok-mapstruct-binding</artifactId>\n                <version>0.2.0</version>\n            </path>\n            <path>\n                <groupId>com.iohao.game</groupId>\n                <artifactId>common-validation</artifactId>\n                <version>${ioGame.version}</version>\n            </path>\n        </annotationProcessorPaths>\n    </configuration>\n</plugin>\n```\n\n"
  },
  {
    "path": "common/common-validation/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>common-validation</artifactId>\n    <name>common-validation for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>common-kit</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>jakarta.validation</groupId>\n            <artifactId>jakarta.validation-api</artifactId>\n            <version>${jakarta.validation-api.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>javax.validation</groupId>\n            <artifactId>validation-api</artifactId>\n            <version>2.0.1.Final</version>\n            <scope>provided</scope>\n        </dependency>\n\n    </dependencies>\n</project>"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/Validation.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.common.validation;\n\nimport com.iohao.game.common.kit.ClassScanner;\nimport com.iohao.game.common.kit.io.ResourceKit;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang.StringUtils;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.stream.Collectors;\n\n/**\n * 数据校验器管理\n *\n * @author shenjk\n * @date 2022-09-26\n */\n@Slf4j\n@UtilityClass\npublic class Validation {\n    final String fileName = \"META-INF/ioGame/com.iohao.game.common.validation.Validator\";\n    final String defaultValidator = \"com.iohao.game.common.validation.support.JakartaValidator\";\n\n    private volatile Validator validator;\n\n    Lock lock = new ReentrantLock();\n\n    /**\n     * 获取当前配置的数据校验器\n     *\n     * @return 数据校验器\n     **/\n    public Validator getValidator() throws Exception {\n        if (validator != null) {\n            return validator;\n        }\n\n        /*\n         * 实际上不加锁也不影响，因为业务框架有个初始化的过程，\n         * 在业务框架初始化时，会用到 validator 变量，\n         * 也就是说在项目启动的过程中，validator 的初始化已经做好了；\n         *\n         * 这里加锁也是为了让验证模块，在其他系统中可以单独的使用，即不依赖 ioGame 也可以安全的使用；\n         */\n\n        lock.lock();\n\n        try {\n\n            if (validator != null) {\n                return validator;\n            }\n\n            final String className = getValidatorClassName();\n            final String packageName = getValidatorPackage(className);\n\n            ClassScanner classScanner = new ClassScanner(packageName, clazz -> clazz.getName().equals(className));\n            List<Class<?>> classList = classScanner.listScan();\n            if (classList.isEmpty()) {\n                throw new ClassNotFoundException(className);\n            }\n\n            Class<?> clazz = classList.getFirst();\n            validator = (Validator) clazz.getConstructor().newInstance();\n\n        } finally {\n            lock.unlock();\n        }\n\n        return validator;\n    }\n\n    /**\n     * 获取数据校验器的类名(fullName)\n     *\n     * @return 校验器的类名\n     */\n    private String getValidatorClassName() {\n        String className = null;\n\n        try {\n            className = ResourceKit.readStr(fileName, StandardCharsets.UTF_8);\n        } catch (Exception e) {\n            log.info(\"Use the default class {} to handle it because reading {} failed.\", defaultValidator, fileName);\n        }\n\n        if (StringUtils.isBlank(className)) {\n            className = defaultValidator;\n        }\n\n        return StringUtils.trim(className);\n    }\n\n    /**\n     * 获取数据校验器的 package\n     *\n     * @return packageName;\n     */\n    private String getValidatorPackage(String className) {\n        List<String> segments = Arrays.stream(className.split(\"\\\\.\")).toList();\n\n        return segments.stream()\n                .limit(segments.size() - 1)\n                .collect(Collectors.joining(\"/\"));\n    }\n}"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/Validator.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\n\npackage com.iohao.game.common.validation;\n\n/**\n * 定义数据校验器接口\n *\n * @author shenjk\n * @date 2022-09-26\n */\npublic interface Validator {\n\n    /**\n     * 执行校验\n     *\n     * @param data   校验数据\n     * @param groups 分组信息\n     * @return 校验不通过的返回信息\n     */\n    String validate(Object data, Class<?>... groups);\n\n    /**\n     * 参数类型是否需要验证\n     *\n     * @param paramClazz 参数类型\n     * @return true 这是一个需要验证的参数\n     */\n    boolean isValidator(Class<?> paramClazz);\n}\n"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/annotation/EnableValidation.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.common.validation.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 通过注解的方式 配置数据校验器\n *\n * @author shenjk\n * @date 2022-09-26\n */\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface EnableValidation {\n\n    /**\n     * 数据校验器的类型\n     */\n    String value() default \"com.iohao.game.common.validation.support.JakartaValidator\";\n}\n"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/annotation/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * JSR380 - annotation\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.validation.annotation;"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * JSR380 - Java Bean Validation，<a href=\"https://iohao.github.io/game/docs/core/jsr380\">开启 JSR380 验证规范</a>\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.validation;"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/processor/ValidationProcessor.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.common.validation.processor;\n\nimport autovalue.shaded.com.google.common.auto.service.AutoService;\nimport com.iohao.game.common.validation.annotation.EnableValidation;\n\nimport javax.annotation.processing.*;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.TypeElement;\nimport javax.tools.Diagnostic;\nimport javax.tools.FileObject;\nimport javax.tools.StandardLocation;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.io.Writer;\nimport java.util.Set;\n\n/**\n * 在 META-INF 生成 ioGame/com.iohao.game.common.validation.Validator 用于游戏服务支持 javax.validation\n * 使用示例：\n * 在 DemoApplication 上加入注解：@EnableValidation(\"com.iohao.game.common.validation.support.JakartaValidator\")\n *\n * @author shenjk\n * @date 2022-09-26\n */\n@AutoService(Processor.class)\n@SupportedAnnotationTypes(\"com.iohao.game.common.validation.annotation.EnableValidation\")\npublic class ValidationProcessor extends AbstractProcessor {\n\n    boolean created = false;\n\n    /**\n     * {@inheritDoc}\n     *\n     * @param annotations\n     * @param roundEnv\n     */\n    @Override\n    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n        if (!roundEnv.processingOver() && !annotations.isEmpty() && !created) {\n            created = true;\n\n            EnableValidation enableValidation = getEnableValidation(roundEnv);\n            if (enableValidation == null) {\n                return true;\n            }\n\n            String className = enableValidation.value();\n            // 生成META-INF/ioGame/com.iohao.game.common.validation.Validator 配置文件\n            createMetaInf(processingEnv, className);\n        }\n\n        return true;\n    }\n\n    /**\n     * 获取EnableValidation注解对象\n     *\n     * @param roundEnv roundEnv\n     */\n    private static EnableValidation getEnableValidation(RoundEnvironment roundEnv) {\n        Set<? extends Element> rootElements = roundEnv.getElementsAnnotatedWith(EnableValidation.class);\n        if (rootElements != null && !rootElements.isEmpty()) {\n            Element element = rootElements.stream().findFirst().get();\n            return element.getAnnotation(EnableValidation.class);\n        }\n\n        return null;\n    }\n\n\n    /**\n     * 生成 META-INF 下的配置文件\n     *\n     * @param processingEnv processingEnv\n     * @param content       content\n     */\n    private static void createMetaInf(ProcessingEnvironment processingEnv, String content) {\n        try {\n\n            FileObject f = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT,\n                    \"\",\n                    \"META-INF/ioGame/com.iohao.game.common.validation.Validator\");\n\n            try (Writer w = f.openWriter()) {\n                PrintWriter pw = new PrintWriter(w);\n                pw.println(content);\n                pw.flush();\n            }\n        } catch (IOException e) {\n            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/processor/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * JSR380 - processor\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.validation.processor;"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/support/JakartaValidator.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.common.validation.support;\n\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.validation.Validator;\nimport jakarta.validation.ConstraintViolation;\nimport jakarta.validation.Validation;\nimport jakarta.validation.ValidatorFactory;\nimport jakarta.validation.metadata.BeanDescriptor;\nimport jakarta.validation.metadata.PropertyDescriptor;\n\nimport java.util.Set;\n\n/**\n * 实现 Jakarta.Validation 数据校验器\n *\n * @author shenjk\n * @date 2022-09-26\n */\npublic class JakartaValidator implements Validator {\n\n    private final jakarta.validation.Validator validator;\n\n    public JakartaValidator() {\n        try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {\n            validator = factory.getValidator();\n        }\n    }\n\n    /**\n     * 执行校验\n     *\n     * @param data   校验数据\n     * @param groups 分组信息\n     * @return 校验不通过的返回信息\n     */\n    @Override\n    public String validate(Object data, Class<?>... groups) {\n        Set<ConstraintViolation<Object>> violationSet = validator.validate(data, groups);\n        if (CollKit.isEmpty(violationSet)) {\n            return null;\n        }\n\n        if (!violationSet.isEmpty()) {\n            final ConstraintViolation<Object> violation = violationSet.iterator().next();\n            String propertyName = violation.getPropertyPath().toString();\n            return propertyName + \" \" + violation.getMessage();\n        }\n\n        return null;\n    }\n\n    /**\n     * 参数类型是否需要验证\n     *\n     * @param paramClazz 参数类型\n     * @return true 这是一个需要验证的参数\n     */\n    @Override\n    public boolean isValidator(Class<?> paramClazz) {\n        // 根据 class 得到 bean 描述\n        BeanDescriptor beanDescriptor = validator.getConstraintsForClass(paramClazz);\n        // bean 的属性上添加的验证注解信息\n        Set<PropertyDescriptor> descriptorSet = beanDescriptor.getConstrainedProperties();\n        return !descriptorSet.isEmpty();\n    }\n}\n"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/support/JavaxValidator.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.common.validation.support;\n\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.validation.Validator;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.metadata.BeanDescriptor;\nimport javax.validation.metadata.PropertyDescriptor;\nimport javax.validation.ValidatorFactory;\nimport java.util.Set;\n\n/**\n * 实现 Javax.Validation 数据校验器\n *\n * @author shenjk\n * @date 2022-09-26\n */\npublic class JavaxValidator implements Validator {\n\n    private final javax.validation.Validator validator;\n\n    public JavaxValidator() {\n        try (ValidatorFactory factory = javax.validation.Validation.buildDefaultValidatorFactory()) {\n            validator = factory.getValidator();\n        }\n    }\n\n    /**\n     * 执行校验\n     *\n     * @param data   校验数据\n     * @param groups 分组信息\n     * @return 校验不通过的返回信息\n     */\n    @Override\n    public String validate(Object data, Class<?>... groups) {\n        Set<ConstraintViolation<Object>> violationSet = validator.validate(data, groups);\n        if (CollKit.isEmpty(violationSet)) {\n            return null;\n        }\n\n        if (!violationSet.isEmpty()) {\n            final javax.validation.ConstraintViolation<Object> violation = violationSet.iterator().next();\n            String propertyName = violation.getPropertyPath().toString();\n            return propertyName + \" \" + violation.getMessage();\n        }\n\n        return null;\n    }\n\n    /**\n     * 参数类型是否需要验证\n     *\n     * @param paramClazz 参数类型\n     * @return true 这是一个需要验证的参数\n     */\n    @Override\n    public boolean isValidator(Class<?> paramClazz) {\n        // 根据 class 得到 bean 描述\n        BeanDescriptor beanDescriptor = validator.getConstraintsForClass(paramClazz);\n        // bean 的属性上添加的验证注解信息\n        Set<PropertyDescriptor> descriptorSet = beanDescriptor.getConstrainedProperties();\n        return !descriptorSet.isEmpty();\n    }\n}\n"
  },
  {
    "path": "common/common-validation/src/main/java/com/iohao/game/common/validation/support/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * JSR380 - 实现类\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.validation.support;"
  },
  {
    "path": "common/common-validation/src/main/resources/META-INF/gradle/incremental.annotation.processors",
    "content": "com.iohao.game.common.validation.processor.ValidationProcessor"
  },
  {
    "path": "common/common-validation/src/main/resources/META-INF/services/javax.annotation.processing.Processor",
    "content": "com.iohao.game.common.validation.processor.ValidationProcessor"
  },
  {
    "path": "doc_maven.txt",
    "content": "建议安装 mvnd，可并发编译\n\nmvnd clean install -Dmaven.test.skip=true\n\n编译\nmvnd compile\n\njprotobuf\nmvnd jprotobuf:precompile\n\n生成 html doc\nmvnd clean install -Dmaven.test.skip=true\n\n依赖相关\nmvnd dependency:tree>doc_game.txt\n\n重复依赖检测，并解决冲突jar\nmvnd enforcer:display-info\nmvnd validate"
  },
  {
    "path": "external/README.md",
    "content": "参考文档 [游戏对外服使用](https://iohao.github.io/game/docs/overall/external_intro)\n\n\n![image](https://github.com/iohao/ioGame/assets/26356013/72ba5121-204b-43a7-85b3-3db051725f02)\n\n**连接方式的支持、切换**\n\nioGame 已提供了 TCP、WebSocket、UDP 连接方式的支持，并提供了灵活的方式来实现连接方式的切换。可以将 TCP、WebSocket、UDP 连接方式与业务代码进行无缝衔接。开发者可以用一套业务代码，无需任何改动，同时支持多种通信协议。\n\n\n\n如果想要切换到不同的连接方式，只需要更改相应的枚举即可，非常简单。在不使用 ioGame 时，将连接方式从 TCP 改为 WebSocket 或 UDP 等，需要进行大量的调整和改动。然而，在 ioGame 中，实现这些转换是非常简单的。此外，不仅可以轻松切换各种连接方式，而且可以同时支持多种连接方式，并使它们在同一应用程序中共存。\n\n\n\n连接方式是可扩展的，而且扩展也简单，这意味着之后如果支持了 KCP，那么将已有项目的连接方式，如 TCP、WebSocket、UDP 切换成 KCP 也是简单的。\n\n\n\n需要再次强调的是，连接方式的切换对业务代码没有任何影响，无需做出任何改动即可实现连接方式的更改。\n\n\n\n**游戏对外服的核心接口**\n\n- ExternalServer：游戏对外服，由 ExternalCore 和 ExternalBrokerClientStartup 组成的一个整体。\n- ExternalCore： 帮助开发者屏蔽各通信框架的细节，如 Netty、mina、smart-socket 等通信框，ioGame 默认提供了基于 Netty 的实现。\n- MicroBootstrap：真实玩家连接的服务器，服务器的创建由 MicroBootstrap 完成，MicroBootstrap 帮助开发者屏蔽连接方式的细节，如 TCP、WebSocket、UDP、KCP 等。目前已经支持 TCP、WebSocket、UDP 的连接方式，而 KCP 的连接方式也在计划内。\n- MicroBootstrapFlow：MicroBootstrapFlow\t与真实玩家连接【真实】服务器的启动流程，专为 MicroBootstrap 服务。开发者可通过此接口对服务器做编排，编排分为：构建时、新建连接时两种。\n\n\n\nMicroBootstrapFlow 接口的目的是尽可能地细化服务器创建和连接时的每个环节，以方便开发者对游戏对外服进行定制化扩展。通常情况下，开发者只需要关注重写 MicroBootstrapFlow.pipelineCustom 方法，就可以实现很强的扩展了。\n\n\n\n"
  },
  {
    "path": "external/external-core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.iohao.game</groupId>\n        <artifactId>ioGame</artifactId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n\n    <artifactId>external-core</artifactId>\n    <name>external-core for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>bolt-client</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/ExternalCore.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core;\n\nimport com.iohao.game.external.core.micro.MicroBootstrap;\n\n/**\n * 与真实玩家连接的 ExternalCore 服务器，也是通信框架屏蔽接口\n * <pre>\n *     ExternalCore 帮助开发者屏蔽各通信框架的细节，如 Netty、mina、smart-socket 等通信框。\n *     ioGame 默认提供 Netty 的实现。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-17\n */\npublic interface ExternalCore {\n    /**\n     * 创建与真实玩家通信的 netty 服务器\n     */\n    MicroBootstrap createMicroBootstrap();\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/ExternalCoreSetting.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core;\n\nimport com.iohao.game.bolt.broker.core.aware.AwareInject;\nimport com.iohao.game.common.kit.attr.AttrOptionDynamic;\n\n/**\n * 与真实玩家连接的 ExternalCore 服务器设置\n * <pre>\n *     由于有动态属性的存在，开发者可以通过此接口做任意的扩展\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-05-05\n */\npublic interface ExternalCoreSetting extends AwareInject, AttrOptionDynamic {\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/ExternalServer.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core;\n\nimport com.iohao.game.external.core.broker.client.ExternalBrokerClientStartup;\n\n/**\n * 由 {@link ExternalCore} 和 {@link ExternalBrokerClientStartup} 组成的一个整体\n * <pre>\n *     主要职责是启动 ExternalCore 和 ExternalBrokerClientStartup\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic interface ExternalServer {\n    /**\n     * 启动游戏对外服\n     */\n    void startup();\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/aware/ExternalCoreSettingAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.aware;\n\nimport com.iohao.game.external.core.ExternalCoreSetting;\n\n/**\n * ExternalCoreSetting Aware\n *\n * @author 渔民小镇\n * @date 2023-05-05\n */\npublic interface ExternalCoreSettingAware {\n    /**\n     * 框架会调用此方法，将 ExternalCoreSetting 对象传入\n     *\n     * @param externalCoreSetting externalCoreSetting\n     */\n    void setExternalCoreSetting(ExternalCoreSetting externalCoreSetting);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/aware/UserSessionsAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.aware;\n\nimport com.iohao.game.external.core.session.UserSessions;\n\n/**\n * UserSessions Aware\n *\n * @author 渔民小镇\n * @date 2023-02-19\n */\npublic interface UserSessionsAware {\n    /**\n     * 框架会调用此方法，将 UserSessions 对象传入\n     *\n     * @param userSessions userSessions\n     */\n    void setUserSessions(UserSessions<?, ?> userSessions);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/aware/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - core - <a href=\"https://iohao.github.io/game/docs/external/aware\">Aware</a>\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.aware;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ExternalBrokerClientStartup.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilder;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.bolt.broker.client.AbstractBrokerClientStartup;\nimport com.iohao.game.bolt.broker.client.processor.BrokerClusterMessageClientProcessor;\nimport com.iohao.game.bolt.broker.client.processor.RequestBrokerClientModuleMessageClientProcessor;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientBuilder;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientType;\nimport com.iohao.game.bolt.broker.core.common.processor.pulse.PulseSignalRequestUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.processor.pulse.PulseSignalResponseUserProcessor;\nimport com.iohao.game.external.core.broker.client.enhance.ExternalEnhances;\nimport com.iohao.game.external.core.broker.client.processor.*;\nimport com.iohao.game.external.core.broker.client.processor.listener.CmdRegionBrokerClientListener;\nimport lombok.Setter;\n\n/**\n * 负责内部通信，与 Broker（游戏网关）通信\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@Setter\npublic class ExternalBrokerClientStartup extends AbstractBrokerClientStartup {\n    String id;\n\n    protected BarSkeletonBuilder createBarSkeletonBuilder() {\n        // 对外服不需要业务框架，这里给个空的\n        BarSkeletonBuilder builder = BarSkeleton.newBuilder();\n        builder.getSetting().setPrint(false);\n        ExternalEnhances.enhance(builder);\n        return builder;\n    }\n\n    @Override\n    public BarSkeleton createBarSkeleton() {\n        return createBarSkeletonBuilder().build();\n    }\n\n    @Override\n    public BrokerClientBuilder createBrokerClientBuilder() {\n        String gameExternalServer = Bundle.getMessage(MessageKey.gameExternalServer);\n\n        return BrokerClient.newBuilder()\n                .id(this.id)\n                .appName(gameExternalServer)\n                // 逻辑服标签 （tag 相当于归类）\n                .tag(\"external\")\n                // 逻辑服设置为对外服类型\n                .brokerClientType(BrokerClientType.EXTERNAL)\n                .addListener(CmdRegionBrokerClientListener.me())\n                ;\n    }\n\n    @Override\n    public void registerUserProcessor(BrokerClientBuilder builder) {\n        builder\n                // 收到网关请求模块信息\n                .registerUserProcessor(RequestBrokerClientModuleMessageClientProcessor::new)\n                // broker （游戏网关）集群处理\n                .registerUserProcessor(BrokerClusterMessageClientProcessor::new)\n                // 注册 广播处理器\n                .registerUserProcessor(BroadcastMessageExternalProcessor::new)\n                // 注册 顺序广播处理器\n                .registerUserProcessor(BroadcastOrderMessageExternalProcessor::new)\n                // 注册 用户id变更处理\n                .registerUserProcessor(SettingUserIdMessageExternalProcessor::new)\n                // 注册 接收网关消息处理\n                .registerUserProcessor(ResponseMessageExternalProcessor::new)\n                // 注册 用户绑定逻辑服\n                .registerUserProcessor(EndPointLogicServerMessageExternalProcessor::new)\n                // 注册 处理来自游戏逻辑服的请求，并响应结果给请求方\n                .registerUserProcessor(RequestCollectExternalMessageExternalProcessor::new)\n                // 脉冲信号请求接收\n                .registerUserProcessor(PulseSignalRequestUserProcessor::new)\n                // 脉冲信号响应接收\n                .registerUserProcessor(PulseSignalResponseUserProcessor::new)\n                // 其他逻辑服的上线、下线相关通知\n                .registerUserProcessor(BrokerClientOnlineMessageExternalProcessor::new)\n                .registerUserProcessor(BrokerClientOfflineMessageExternalProcessor::new)\n        ;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/enhance/ExternalEnhance.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.enhance;\n\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilder;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-16\n */\npublic interface ExternalEnhance {\n    void enhance(BarSkeletonBuilder builder);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/enhance/ExternalEnhances.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.enhance;\n\nimport com.iohao.game.action.skeleton.core.BarSkeletonBuilder;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.ServiceLoader;\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-16\n */\n@Slf4j\n@UtilityClass\npublic class ExternalEnhances {\n    final Set<ExternalEnhance> enhanceSet = new NonBlockingHashSet<>();\n\n    static {\n        ServiceLoader.load(ExternalEnhance.class).forEach(ExternalEnhances::add);\n    }\n\n    void add(ExternalEnhance enhance) {\n        enhanceSet.add(enhance);\n    }\n\n    public void enhance(BarSkeletonBuilder builder) {\n        for (ExternalEnhance enhance : enhanceSet) {\n            enhance.enhance(builder);\n        }\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.bolt.broker.client.kit.ExternalCommunicationKit;\nimport com.iohao.game.external.core.broker.client.ext.impl.ExistUserExternalBizRegion;\nimport com.iohao.game.external.core.broker.client.ext.impl.ForcedOfflineExternalBizRegion;\n\nimport java.io.Serializable;\n\nimport com.iohao.game.core.common.client.ExternalBizCodeCont;\n\n/**\n * 对外服业务扩展\n * <pre>\n *     开发者可以通过实现这个接口，向游戏逻辑服提供一些\n *     1 只存在于游戏对外服中的数据\n *     2 只有游戏对外服可以做的事\n *\n *     框架提供了两个参考实现\n *     {@link  ExistUserExternalBizRegion} 查询用户（玩家）是否在线\n *     {@link  ForcedOfflineExternalBizRegion} 强制用户（玩家）下线\n *\n *     开发者扩展完后，需要添加到 {@link ExternalBizRegions#add(ExternalBizRegion)} 才会生效\n *\n *     使用请参考 {@link  ExternalCommunicationKit}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-21\n * @see ExternalBizCodeCont\n */\npublic interface ExternalBizRegion {\n    /**\n     * 业务码\n     * <pre>\n     *     开发者扩展时，用正数的业务码\n     * </pre>\n     *\n     * @return 业务码\n     */\n    int getBizCode();\n\n    /**\n     * 业务处理\n     * <pre>\n     *     返回的数据会存放到 ResponseCollectExternalItemMessage.data 中\n     * </pre>\n     *\n     * @param regionContext 对外服业务处理上下文\n     * @return 业务数据，如果有需要传递给请求端的数据，可以在此返回\n     * @throws MsgException e\n     */\n    Serializable request(ExternalBizRegionContext regionContext) throws MsgException;\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegionContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext;\n\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.external.core.session.UserSessions;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ExternalBizRegionContext {\n    RequestCollectExternalMessage requestCollectExternalMessage;\n    UserSessions<?, ?> userSessions;\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext;\n\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.external.core.broker.client.ext.impl.*;\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\n\n/**\n * 对外服扩展的业务域集合\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@UtilityClass\npublic final class ExternalBizRegions {\n\n    /**\n     * <pre>\n     *     key : 业务码\n     *     value : 对应的业务处理器\n     * </pre>\n     */\n    Map<Integer, ExternalBizRegion> regionMap = new NonBlockingHashMap<>();\n\n    static {\n        // 框架默认提供的业务类\n        add(new ExistUserExternalBizRegion());\n        add(new ForcedOfflineExternalBizRegion());\n        add(new AttachmentExternalBizRegion());\n        add(new UserHeadMetadataExternalBizRegion());\n    }\n\n    public void add(ExternalBizRegion externalBizRegion) {\n        int bizCode = externalBizRegion.getBizCode();\n\n        if (regionMap.containsKey(bizCode)) {\n            // 重复添加已经存在的业务码\n            ThrowKit.ofRuntimeException(\"Already exists : \" + bizCode);\n        }\n\n        regionMap.put(bizCode, externalBizRegion);\n    }\n\n    public ExternalBizRegion getExternalRegion(int bizCode) {\n        return regionMap.get(bizCode);\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/AttachmentExternalBizRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext.impl;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.core.common.client.ExternalBizCodeCont;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegion;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;\nimport com.iohao.game.external.core.session.UserSessionOption;\n\nimport java.io.Serializable;\n\n/**\n * 元信息\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic final class AttachmentExternalBizRegion implements ExternalBizRegion {\n    @Override\n    public int getBizCode() {\n        return ExternalBizCodeCont.attachment;\n    }\n\n    @Override\n    public Serializable request(ExternalBizRegionContext regionContext) throws MsgException {\n        // 检测用户是否存在\n        ExternalBizRegionKit.checkUserExist(regionContext);\n\n        RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();\n        long userId = request.getUserId();\n        byte[] bytes = request.getData();\n\n        var userSessions = regionContext.getUserSessions();\n        userSessions.ifPresent(userId, userSession -> userSession.option(UserSessionOption.attachment, bytes));\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExistUserExternalBizRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext.impl;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.core.common.client.ExternalBizCodeCont;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegion;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;\n\nimport java.io.Serializable;\n\n/**\n * 用户（玩家）是否存在\n * <pre>\n *     对外服业务扩展\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic final class ExistUserExternalBizRegion implements ExternalBizRegion {\n\n    @Override\n    public int getBizCode() {\n        return ExternalBizCodeCont.existUser;\n    }\n\n    @Override\n    public Serializable request(ExternalBizRegionContext regionContext) throws MsgException {\n        // 检测用户是否存在\n        ExternalBizRegionKit.checkUserExist(regionContext);\n        return null;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExternalBizRegionKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext.impl;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author 渔民小镇\n * @date 2024-04-18\n */\n@UtilityClass\nclass ExternalBizRegionKit {\n    /**\n     * 检测用户是否存在，如果不存在就抛异常\n     *\n     * @param regionContext regionContext\n     */\n    public void checkUserExist(ExternalBizRegionContext regionContext) {\n        RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();\n        // 检测用户是否存在\n        long userId = request.getUserId();\n        var userSessions = regionContext.getUserSessions();\n        boolean existUser = userSessions.existUserSession(userId);\n        ActionErrorEnum.dataNotExist.assertTrue(existUser);\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ForcedOfflineExternalBizRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext.impl;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.core.common.client.ExternalBizCodeCont;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegion;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\n\nimport java.io.Serializable;\n\n/**\n * 强制用户（玩家）下线\n * <pre>\n *     对外服业务扩展\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic final class ForcedOfflineExternalBizRegion implements ExternalBizRegion {\n    final BarMessage response;\n\n    public ForcedOfflineExternalBizRegion() {\n        // 强制玩家下线 状态码\n        response = ExternalCodecKit.createResponse();\n        response.setResponseStatus(ActionErrorEnum.forcedOffline.getCode());\n        response.setValidatorMsg(ActionErrorEnum.forcedOffline.getMsg());\n    }\n\n    @Override\n    public int getBizCode() {\n        return ExternalBizCodeCont.forcedOffline;\n    }\n\n    @Override\n    public Serializable request(ExternalBizRegionContext regionContext) {\n        RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();\n\n        long userId = request.getUserId();\n\n        // 发送强制下线消息\n        var userSessions = regionContext.getUserSessions();\n        // 据 userId 移除 UserSession ，在移除前发送一个消息\n        userSessions.removeUserSession(userId, response);\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/UserHeadMetadataExternalBizRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.ext.impl;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.core.common.client.ExternalBizCodeCont;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegion;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;\nimport com.iohao.game.external.core.session.UserSession;\n\nimport java.io.Serializable;\n\n/**\n * 从用户（玩家）所在游戏对外服中获取用户自身的数据\n *\n * <pre>\n *     因为用户（玩家）的数据是存在 UserSession 中的，see {@link UserSession#employ(BarMessage)}，通过该扩展，我们能获取这些数据。\n *     employ 中的数据包括：userId、用户所绑定的游戏逻辑服、元信息 ...等，更具体的请阅读源码。\n *\n *     使用场景：\n *         在模拟玩家请求时，需要用到玩家的元信息、已绑定游戏逻辑服...等相关信息时，可以从这里（UserHeadMetadataExternalBizRegion）获取。\n *         如果需要业务的场景不复杂（也就是不需要玩家元信息、已绑定游戏逻辑服...等相关信息时），建议使用模拟玩家请求（常规的跨服调用）。\n *\n *         实际中，使用常规的模拟玩家请求就能满足大部分业务场景的需求了。\n *\n *     注意事项：\n *         需要玩家是在线的，也就是接入了其中一个游戏对外服的。\n *\n *     使用方式\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-04-18\n */\npublic final class UserHeadMetadataExternalBizRegion implements ExternalBizRegion {\n    @Override\n    public int getBizCode() {\n        return ExternalBizCodeCont.userHeadMetadata;\n    }\n\n    @Override\n    public Serializable request(ExternalBizRegionContext regionContext) throws MsgException {\n        // 检测用户是否存在\n        ExternalBizRegionKit.checkUserExist(regionContext);\n\n        RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();\n\n        HeadMetadata headMetadata = request.getData();\n\n        long userId = request.getUserId();\n        var userSessions = regionContext.getUserSessions();\n        UserSession userSession = userSessions.getUserSession(userId);\n\n        // 给 message（RequestMessage） 加上一些 user 自身的数据\n        userSession.employ(headMetadata);\n\n        return headMetadata;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - broker client - <a href=\"https://iohao.github.io/game/docs/communication/external_biz_region\">访问游戏对外服与扩展 - 实现</a>\n * @author 渔民小镇\n * @date 2024-10-15\n */\npackage com.iohao.game.external.core.broker.client.ext.impl;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - broker client - <a href=\"https://iohao.github.io/game/docs/communication/external_biz_region\">访问游戏对外服与扩展 - 接口</a>\n * @author 渔民小镇\n * @date 2024-10-15\n */\npackage com.iohao.game.external.core.broker.client.ext;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - broker client\n *\n * @author 渔民小镇\n * @date 2024-10-15\n */\npackage com.iohao.game.external.core.broker.client;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/BroadcastMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BroadcastMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.session.UserSessions;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 接收并处理 来自网关的广播消息\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@Slf4j(topic = IoGameLogName.MsgTransferTopic)\npublic final class BroadcastMessageExternalProcessor extends AbstractAsyncUserProcessor<BroadcastMessage>\n        implements UserSessionsAware {\n    UserSessions<?, ?> userSessions;\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BroadcastMessage message) {\n        if (IoGameGlobalConfig.isExternalLog()) {\n            log.info(\"对外服接收网关的数据：{}\", message);\n        }\n\n        ExternalCodecKit.broadcast(message, this.userSessions);\n    }\n\n    @Override\n    public String interest() {\n        return BroadcastMessage.class.getName();\n    }\n\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/BroadcastOrderMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.message.BroadcastOrderMessage;\nimport com.iohao.game.common.kit.ExecutorKit;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.session.UserSessions;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * 接收并处理 来自网关的广播消息 - 顺序的\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic final class BroadcastOrderMessageExternalProcessor extends AsyncUserProcessor<BroadcastOrderMessage>\n        implements UserSessionsAware {\n\n    final ExecutorService executorService = ExecutorKit.newSingleThreadExecutor(\"BroadcastOrderExternal\");\n    UserSessions<?, ?> userSessions;\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BroadcastOrderMessage message) {\n        ExternalCodecKit.broadcast(message, this.userSessions);\n    }\n\n    @Override\n    public Executor getExecutor() {\n        return executorService;\n    }\n\n    @Override\n    public String interest() {\n        return BroadcastOrderMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/BrokerClientOfflineMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.bolt.broker.client.processor.BrokerClientLineKit;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientOfflineMessage;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.external.core.hook.BrokerClientExternalAttr;\nimport lombok.Setter;\n\n/**\n * 逻辑服下线通知\n *\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Setter\npublic final class BrokerClientOfflineMessageExternalProcessor extends AbstractAsyncUserProcessor<BrokerClientOfflineMessage>\n        implements CmdRegionsAware, BrokerClientAware {\n\n    BrokerClient brokerClient;\n    CmdRegions cmdRegions;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BrokerClientOfflineMessage message) {\n\n        // BrokerClientOfflineMessage 是 Broker（游戏网关）发送过来的信息，表示有逻辑服下线\n        BrokerClientModuleMessage moduleMessage = message.getModuleMessage();\n\n        BrokerClientLineKit.executeSafe(moduleMessage, () -> {\n            // offline process\n            brokerClient.option(BrokerClientExternalAttr.cmdRegions, cmdRegions);\n            BrokerClientLineKit.offlineProcess(moduleMessage, brokerClient);\n        });\n    }\n\n    @Override\n    public String interest() {\n        return BrokerClientOfflineMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/BrokerClientOnlineMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.bolt.broker.client.processor.BrokerClientLineKit;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientOnlineMessage;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.external.core.hook.BrokerClientExternalAttr;\nimport lombok.Setter;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Setter\npublic final class BrokerClientOnlineMessageExternalProcessor extends AbstractAsyncUserProcessor<BrokerClientOnlineMessage>\n        implements CmdRegionsAware, BrokerClientAware {\n\n    BrokerClient brokerClient;\n    CmdRegions cmdRegions;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BrokerClientOnlineMessage message) {\n        // 在线上的其他逻辑服（其他逻辑服指的是除自己外的其他游戏对外服、其他游戏逻辑服）\n        BrokerClientModuleMessage moduleMessage = message.getModuleMessage();\n\n        BrokerClientLineKit.executeSafe(moduleMessage, () -> {\n            // online process\n            brokerClient.option(BrokerClientExternalAttr.cmdRegions, cmdRegions);\n            BrokerClientLineKit.onlineProcess(moduleMessage, brokerClient);\n        });\n    }\n\n    @Override\n    public String interest() {\n        return BrokerClientOnlineMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/EndPointLogicServerMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.protocol.processor.EndPointLogicServerMessage;\nimport com.iohao.game.action.skeleton.protocol.processor.EndPointOperationEnum;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.HashKit;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.session.UserSessionOption;\nimport com.iohao.game.external.core.session.UserSessions;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic final class EndPointLogicServerMessageExternalProcessor extends AbstractAsyncUserProcessor<EndPointLogicServerMessage>\n        implements UserSessionsAware {\n    UserSessions<?, ?> userSessions;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, EndPointLogicServerMessage message) {\n        List<Long> userList = message.getUserList();\n        EndPointOperationEnum operation = message.getOperation();\n        if (CollKit.isEmpty(userList) || Objects.isNull(operation)) {\n            return;\n        }\n\n        List<Integer> collect = listLogicServerId(message);\n\n        this.userSessions.ifPresent(userList, userSession -> {\n            // 将用户绑定（关联）到游戏逻辑服，所有与该游戏逻辑服相关的请求都由该服务器处理。\n            userSession.ifPresent(UserSessionOption.bindingLogicServerIdSet, bindingLogicServerIdSet -> {\n\n                switch (operation) {\n                    case APPEND_BINDING:\n                        bindingLogicServerIdSet.addAll(collect);\n                        break;\n                    case COVER_BINDING:\n                        bindingLogicServerIdSet.clear();\n                        bindingLogicServerIdSet.addAll(collect);\n                        break;\n                    case REMOVE_BINDING:\n                        collect.forEach(bindingLogicServerIdSet::remove);\n                        break;\n                    default:\n                        bindingLogicServerIdSet.clear();\n                }\n\n                int[] data = null;\n                if (!bindingLogicServerIdSet.isEmpty()) {\n                    data = bindingLogicServerIdSet.stream().mapToInt(Integer::intValue).toArray();\n                }\n\n                userSession.option(UserSessionOption.bindingLogicServerIdArray, data);\n            });\n        });\n    }\n\n    private List<Integer> listLogicServerId(EndPointLogicServerMessage message) {\n        Set<String> logicServerIdSet = message.getLogicServerIdSet();\n        if (Objects.isNull(logicServerIdSet)) {\n            return Collections.emptyList();\n        }\n\n        return logicServerIdSet\n                .stream()\n                .map(HashKit::hash32)\n                .toList();\n    }\n\n    @Override\n    public String interest() {\n        return EndPointLogicServerMessage.class.getName();\n    }\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/RequestCollectExternalMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalItemMessage;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.trace.TraceKit;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegion;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;\nimport com.iohao.game.external.core.broker.client.ext.ExternalBizRegions;\nimport com.iohao.game.external.core.session.UserSessions;\nimport lombok.extern.slf4j.Slf4j;\nimport org.slf4j.MDC;\n\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n * 处理来自游戏逻辑服的请求，并响应结果给请求方\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@Slf4j(topic = IoGameLogName.ExternalTopic)\npublic final class RequestCollectExternalMessageExternalProcessor extends AbstractAsyncUserProcessor<RequestCollectExternalMessage>\n        implements UserSessionsAware {\n    UserSessions<?, ?> userSessions;\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestCollectExternalMessage request) {\n\n        int bizCode = request.getBizCode();\n\n        // 通过业务码得到对应的业务处理类\n        ExternalBizRegion externalBizRegion = ExternalBizRegions.getExternalRegion(bizCode);\n\n        // 通常是开发者忘记配置对应的处理类\n        if (Objects.isNull(externalBizRegion)) {\n            log.error(\"{} - 游戏对外服对应的业务类不存在\", bizCode);\n\n            // 对外服业务处理不存在\n            var itemMessage = new ResponseCollectExternalItemMessage();\n            itemMessage.setError(ActionErrorEnum.classNotExist);\n            // 返回结果给请求端\n            asyncCtx.sendResponse(itemMessage);\n\n            return;\n        }\n\n        var itemMessage = new ResponseCollectExternalItemMessage();\n\n        ExternalBizRegionContext context = new ExternalBizRegionContext();\n        context.setRequestCollectExternalMessage(request);\n        context.setUserSessions(this.userSessions);\n\n        var hasTraceId = Objects.nonNull(request.getTraceId());\n\n        try {\n            if (hasTraceId) {\n                MDC.put(TraceKit.traceName, request.getTraceId());\n            }\n\n            Serializable data = externalBizRegion.request(context);\n            itemMessage.setData(data);\n        } catch (Throwable e) {\n            if (e instanceof MsgException msgException) {\n                itemMessage.setError(msgException);\n            } else {\n                // 不是自定义的异常才打印 error 信息\n                log.error(e.getMessage(), e);\n                itemMessage.setError(ActionErrorEnum.systemOtherErrCode);\n            }\n        } finally {\n            if (hasTraceId) {\n                MDC.clear();\n            }\n        }\n\n        // 返回结果给请求端\n        asyncCtx.sendResponse(itemMessage);\n    }\n\n    @Override\n    public String interest() {\n        return RequestCollectExternalMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/ResponseMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.message.ExternalMessageCmdCode;\nimport com.iohao.game.external.core.session.UserChannelId;\nimport com.iohao.game.external.core.session.UserSession;\nimport com.iohao.game.external.core.session.UserSessions;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\n\n/**\n * 接收来自网关的响应\n * <pre>\n *     把响应 write 到客户端\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@Slf4j(topic = IoGameLogName.MsgTransferTopic)\npublic final class ResponseMessageExternalProcessor extends AbstractAsyncUserProcessor<ResponseMessage>\n        implements UserSessionsAware {\n    final UserChannelId emptyUserChannelId = new UserChannelId(\"empty\");\n    UserSessions<?, ?> userSessions;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, ResponseMessage responseMessage) {\n        if (IoGameGlobalConfig.isExternalLog()) {\n            log.info(\"接收来自网关的响应 {}\", responseMessage);\n        }\n\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        headMetadata.setCmdCode(ExternalMessageCmdCode.biz);\n\n        long userId = headMetadata.getUserId();\n        // 当存在 userId 时，并且可以找到对应的 UserSession\n        UserSession userSession;\n        if (userId > 0) {\n            userSession = userSessions.getUserSession(userId);\n        } else {\n            // 通过 channelId 来查找 UserSession\n            String channelId = headMetadata.getChannelId();\n            final UserChannelId userChannelId = Objects.isNull(channelId)\n                    ? emptyUserChannelId\n                    : new UserChannelId(channelId);\n\n            userSession = userSessions.getUserSession(userChannelId);\n        }\n\n        // 响应结果给用户\n        if (userSession != null) {\n            userSession.writeAndFlush(responseMessage);\n        }\n\n        // 游戏对外服缓存\n        int cacheCondition = headMetadata.getCacheCondition();\n        // 当缓存条件存在时，表示需要缓存数据\n        if (cacheCondition != 0) {\n            // 能到这里，externalCmdCache 一定不为 null\n            ExternalGlobalConfig.externalCmdCache.addCacheData(responseMessage);\n        }\n    }\n\n    @Override\n    public String interest() {\n        return ResponseMessage.class.getName();\n    }\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/SettingUserIdMessageExternalProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.protocol.login.SettingUserIdMessage;\nimport com.iohao.game.action.skeleton.protocol.login.SettingUserIdMessageResponse;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.common.kit.trace.TraceKit;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.session.UserChannelId;\nimport com.iohao.game.external.core.session.UserSessions;\nimport org.slf4j.MDC;\n\nimport java.util.Objects;\n\n/**\n * 设置 userId 的处理器\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic final class SettingUserIdMessageExternalProcessor extends AbstractAsyncUserProcessor<SettingUserIdMessage>\n        implements UserSessionsAware {\n    UserSessions<?, ?> userSessions;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, SettingUserIdMessage request) {\n        var headMetadata = request.getHeadMetadata();\n\n        String channelId = headMetadata.getChannelId();\n        var userChannelId = new UserChannelId(channelId);\n\n        long userId = request.getUserId();\n        var response = new SettingUserIdMessageResponse();\n        response.setUserId(userId);\n\n        String traceId = headMetadata.getTraceId();\n        if (Objects.nonNull(traceId)) {\n            try {\n                MDC.put(TraceKit.traceName, traceId);\n                // 当设置好玩家 id ，也表示着已经身份验证了（表示登录过了）。\n                boolean result = this.userSessions.settingUserId(userChannelId, userId);\n                response.setSuccess(result);\n            } finally {\n                MDC.clear();\n            }\n        } else {\n            // 当设置好玩家 id ，也表示着已经身份验证了（表示登录过了）。\n            boolean result = this.userSessions.settingUserId(userChannelId, userId);\n            response.setSuccess(result);\n        }\n\n        asyncCtx.sendResponse(response);\n    }\n\n    @Override\n    public String interest() {\n        return SettingUserIdMessage.class.getName();\n    }\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/listener/CmdRegionBrokerClientListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.broker.client.processor.listener;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.common.processor.listener.BrokerClientListener;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.core.common.cmd.BrokerClientId;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.external.core.hook.BrokerClientExternalAttr;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\npublic class CmdRegionBrokerClientListener implements BrokerClientListener {\n\n    @Override\n    public void onlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        CmdRegions cmdRegions = client.option(BrokerClientExternalAttr.cmdRegions);\n        // 游戏逻辑服的路由数据\n        cmdRegions.loading(otherModuleMessage);\n    }\n\n    @Override\n    public void offlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        CmdRegions cmdRegions = client.option(BrokerClientExternalAttr.cmdRegions);\n\n        String id = otherModuleMessage.getId();\n        int idHash = otherModuleMessage.getIdHash();\n        BrokerClientId brokerClientId = new BrokerClientId(idHash, id);\n        // 游戏逻辑服的路由数据\n        cmdRegions.unLoading(brokerClientId);\n    }\n\n    public static CmdRegionBrokerClientListener me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final CmdRegionBrokerClientListener ME = new CmdRegionBrokerClientListener();\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/listener/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - broker client listener\n * @author 渔民小镇\n * @date 2024-10-15\n */\npackage com.iohao.game.external.core.broker.client.processor.listener;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - broker client processor\n * @author 渔民小镇\n * @date 2024-10-15\n */\npackage com.iohao.game.external.core.broker.client.processor;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/config/ExternalGlobalConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.config;\n\nimport com.iohao.game.external.core.hook.AccessAuthenticationHook;\nimport com.iohao.game.external.core.hook.cache.ExternalCmdCache;\nimport com.iohao.game.external.core.hook.internal.DefaultAccessAuthenticationHook;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@UtilityClass\npublic class ExternalGlobalConfig {\n    /** 游戏对外服默认启动端口 */\n    public final int externalPort = 10100;\n\n    /**\n     * 访问验证钩子接口\n     */\n    public AccessAuthenticationHook accessAuthenticationHook = new DefaultAccessAuthenticationHook();\n    /** 游戏对外服路由缓存 */\n    public ExternalCmdCache externalCmdCache;\n    /** true 表示开启简单日志打印 netty handler. see SimpleLoggerHandler */\n    public boolean enableLoggerHandler = true;\n    /**\n     * 协议开关，用于一些协议级别的开关控制，比如 安全加密校验等。 : 0 不校验\n     * <pre>\n     *     see {@link  ExternalMessage#getProtocolSwitch()}\n     * </pre>\n     */\n    public int protocolSwitch;\n\n    @UtilityClass\n    public class CoreOption {\n        /** 默认数据包最大 1MB */\n        public int packageMaxSize = 1024 * 1024;\n        /** http 升级 websocket 协议地址 */\n        public String websocketPath = \"/websocket\";\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/config/ExternalJoinEnum.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.config;\n\nimport lombok.Getter;\n\n/**\n * 连接方式\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\n@Getter\npublic enum ExternalJoinEnum {\n    /**\n     * ext socket\n     * <pre>\n     *     特殊的预留扩展\n     * </pre>\n     */\n    EXT_SOCKET(\"ext socket\", 0),\n    /** TCP socket */\n    TCP(\"TCP\", 1),\n    /** WebSocket */\n    WEBSOCKET(\"WebSocket\", 2),\n    /** UDP socket */\n    UDP(\"UDP\", 3);\n\n    final String name;\n    final int index;\n\n    ExternalJoinEnum(String name, int index) {\n        this.name = name;\n        this.index = index;\n    }\n\n    /**\n     * 根据所使用的连接方式，协定变化的端口\n     * <a href=\"https://github.com/iohao/ioGame/issues/159\">同时支持多种通信方式</a>\n     * <pre>\n     *     ws  port = port;\n     *     tcp port = port + 1;\n     *     udp port = port + 2;\n     *\n     *     这个用法一般出现在，同一进程内启动了多个不同连接方式的游戏对外服（websocket、tcp、udp），\n     *     如果没有这方面需求的，不需要使用该方法。\n     * </pre>\n     *\n     * @param port 端口，玩家连接的端口\n     * @return 协定变化的端口\n     */\n    public int cocPort(int port) {\n        return switch (this) {\n            case TCP -> port + 1;\n            case UDP -> port + 2;\n            default -> port;\n        };\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/config/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - 配置相关\n *\n * @author 渔民小镇\n * @date 2024-10-15\n */\npackage com.iohao.game.external.core.config;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/AccessAuthenticationHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook;\n\nimport com.iohao.game.external.core.session.UserSession;\n\n/**\n * 路由访问权限的控制\n * <pre>\n *     参考 <a href=\"https://iohao.github.io/game/docs/external/access_authentication\">路由访问权限的控制-文档</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-19\n */\npublic interface AccessAuthenticationHook {\n\n    /**\n     * 表示登录才能访问业务方法\n     *\n     * @param verifyIdentity true 需要登录才能访问业务方法\n     */\n    void setVerifyIdentity(boolean verifyIdentity);\n\n    /**\n     * 添加需要忽略的路由，这些忽略的路由不需要登录也能访问\n     *\n     * @param cmd    cmd\n     * @param subCmd subCmd\n     */\n    void addIgnoreAuthCmd(int cmd, int subCmd);\n\n    /**\n     * 添加需要忽略的主路由，这些忽略的主路由不需要登录也能访问\n     *\n     * @param cmd 主路由\n     */\n    void addIgnoreAuthCmd(int cmd);\n\n    /**\n     * 移除需要忽略的路由\n     *\n     * @param cmd    cmd\n     * @param subCmd subCmd\n     */\n    void removeIgnoreAuthCmd(int cmd, int subCmd);\n\n    /**\n     * 移除需要忽略的路由\n     *\n     * @param cmd cmd\n     */\n    void removeIgnoreAuthCmd(int cmd);\n\n    /**\n     * 访问验证\n     * <pre>\n     *     通过的验证，可以访问游戏逻辑服的业务方法\n     * </pre>\n     *\n     * @param loginSuccess true 表示玩家登录成功 {@link UserSession#isVerifyIdentity()}\n     * @param cmdMerge     路由\n     * @return true 通过访问验证\n     */\n    boolean pass(boolean loginSuccess, int cmdMerge);\n\n    /**\n     * 添加拒绝访问的主路由，这些主路由不能由外部直接访问\n     * <pre>\n     *     这里的外部指的是玩家\n     * </pre>\n     *\n     * @param cmd 主路由\n     */\n    void addRejectionCmd(int cmd);\n\n    /**\n     * 添加拒绝访问的路由，这些路由不能由外部直接访问\n     * <pre>\n     *     这里的外部指的是玩家\n     * </pre>\n     *\n     * @param cmd    主路由\n     * @param subCmd 子路由\n     */\n    void addRejectionCmd(int cmd, int subCmd);\n\n    /**\n     * 移除拒绝访问的路由\n     *\n     * @param cmd    主路由\n     * @param subCmd 子路由\n     */\n    void removeRejectCmd(int cmd, int subCmd);\n\n    /**\n     * 移除拒绝访问的路由\n     *\n     * @param cmd 主路由\n     */\n    void removeRejectCmd(int cmd);\n\n    /**\n     * 拒绝访问的路由\n     * <pre>\n     *     当为 true 时，玩家不能访问此路由地址\n     * </pre>\n     *\n     * @param cmdMerge 路由\n     * @return true 表示玩家不能访问此路由\n     */\n    boolean reject(int cmdMerge);\n\n    /**\n     * 清除所有的忽略的路由和拒绝路由数据配置\n     */\n    void clear();\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/BrokerClientExternalAttr.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook;\n\nimport com.iohao.game.common.kit.attr.AttrOption;\nimport com.iohao.game.core.common.cmd.CmdRegions;\n\n/**\n * 游戏对外服的扩展属性\n *\n * @author 渔民小镇\n * @date 2023-12-14\n */\npublic interface BrokerClientExternalAttr {\n    AttrOption<CmdRegions> cmdRegions = AttrOption.valueOf(\"cmdRegions\");\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/IdleHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.session.UserSession;\n\n/**\n * 心跳相关\n * <pre>\n *     参考 <a href=\"https://iohao.github.io/game/docs/external/idle\">心跳设置与心跳钩子-文档</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic interface IdleHook<IdleEvent> {\n    /**\n     * 心跳事件回调\n     * <pre>\n     *     这里只需要做你的业务就可以了，比如通知房间内的其他玩家，该用户下线了。\n     * </pre>\n     *\n     * @param userSession userSession\n     * @param event       event\n     * @return true 断开玩家连接\n     */\n    boolean callback(UserSession userSession, IdleEvent event);\n\n    /**\n     * 心跳响应前的回调\n     * <pre>\n     *     开发者可以给心跳消息添加一些额外信息，比如当前时间之类的。\n     * </pre>\n     * example\n     * <pre>{@code\n     *     @Override\n     *     public void pongBefore(BarMessage idleMessage) {\n     *         // 把当前时间戳给到心跳接收端\n     *         LongValue data = LongValue.of(TimeKit.currentTimeMillis());\n     *         idleMessage.setData(data);\n     *     }\n     * }\n     * </pre>\n     *\n     * @param idleMessage 心跳消息\n     */\n    default void pongBefore(BarMessage idleMessage) {\n\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/UserHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook;\n\nimport com.iohao.game.external.core.session.UserSession;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\n\n/**\n * UserHook 钩子接口，上线时、下线时会触发; <a href=\"https://iohao.github.io/game/docs/external/user_hook\">用户上线、下线的钩子-文档</a>\n *\n * <pre>\n *     实际上需要真正登录过，才会触发 ：into 和 quit 方法\n *     see {@link FlowContext#setUserId(long)}\n *\n *     这里是改变用户的验证状态\n *\n *     验证状态变更为 true -------- 真正登录过\n *     see {@link UserSession#setUserId}\n *     channel.attr(UserSessionAttr.verifyIdentity).set(true);\n *\n *     利用好该接口，可以把用户当前在线状态通知到逻辑服，比如使用 redis PubSub 之类的。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-20\n */\npublic interface UserHook {\n    /**\n     * 用户进入，可以理解为上线\n     *\n     * @param userSession userSession\n     */\n    void into(UserSession userSession);\n\n    /**\n     * 用户退出，可以理解为下线、离线通知等\n     *\n     * @param userSession userSession\n     */\n    void quit(UserSession userSession);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/cache/CmdCacheOption.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook.cache;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.time.Duration;\nimport java.util.Objects;\n\n/**\n * 游戏对外服缓存配置\n *\n * @author 渔民小镇\n * @date 2023-07-02\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class CmdCacheOption {\n    /** 过期时间 */\n    final Duration expireTime;\n    /** cmdActionCache 内的缓存数量 */\n    final int cacheLimit;\n    /** 缓存过期检测时间周期 */\n    final Duration expireCheckTime;\n\n    private CmdCacheOption(Duration expireTime, int cacheLimit, Duration expireCheckTime) {\n        this.expireTime = expireTime;\n        this.cacheLimit = cacheLimit;\n        this.expireCheckTime = expireCheckTime;\n    }\n\n    public static CmdCacheOption.Builder newBuilder() {\n        return new Builder();\n    }\n\n    @Setter\n    @Accessors(chain = true)\n    @FieldDefaults(level = AccessLevel.PRIVATE)\n    public final static class Builder {\n        /** 过期时间 */\n        Duration expireTime = Duration.ofHours(1);\n\n        /**\n         * 缓存数量（同一个 action 的缓存数量上限）\n         * <pre>\n         *     因为游戏对外服缓存支持对应条件与缓存数据关联，所以这里有必要做个缓存数据上限，目的是防止客户端恶意制造无效的查询条件\n         * </pre>\n         */\n        int cacheLimit = 256;\n\n        /**\n         * 缓存过期检测时间\n         * <pre>\n         *     间隔多久做一次缓存过期检测\n         *\n         *     默认是每 5 分钟做一次缓存数据的检测\n         *\n         *     注意事项：\n         *         设置缓存过期检测时间，是为了避免频繁的对缓存做检测，所以缓存的过期时间会有一些误差。\n         *         误差范围值 = expireTime (+-) expireCheckTime\n         *\n         *         如果你想很精准的控制缓存时间，可以设置为每秒做一次检测。\n         * </pre>\n         */\n        Duration expireCheckTime = Duration.ofMinutes(5);\n\n        public CmdCacheOption build() {\n\n            Objects.requireNonNull(expireTime);\n\n            if (cacheLimit <= 0) {\n                cacheLimit = 256;\n            }\n\n            return new CmdCacheOption(this.expireTime, this.cacheLimit, this.expireCheckTime);\n        }\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/cache/ExternalCmdCache.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook.cache;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\n\n/**\n * 游戏对外服缓存数据查询、添加相关接口\n *\n * @author 渔民小镇\n * @date 2023-07-02\n */\npublic interface ExternalCmdCache extends ExternalCmdCacheSetting {\n\n    /**\n     * 查询：从缓存中取数据\n     * <pre>\n     *     当从缓存中找到数据时，会复用 ExternalMessage 对象（引用不变）。\n     *     将缓存数据设置到 ExternalMessage.data 中，避免一次对象的创建。\n     * </pre>\n     *\n     * @param message message\n     * @return 返回值为 null，表示缓存中没有数据\n     */\n    BarMessage getCache(BarMessage message);\n\n    /**\n     * 添加：将响应数据添加到缓存中\n     *\n     * @param responseMessage responseMessage\n     */\n    void addCacheData(ResponseMessage responseMessage);\n\n    /**\n     * 创建 ExternalCmdCache 默认实现类\n     *\n     * @return ExternalCmdCache\n     * @since 21.17\n     */\n    static ExternalCmdCache of() {\n        return new SimpleExternalCmdCache();\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/cache/ExternalCmdCacheSetting.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook.cache;\n\n/**\n * 游戏对外服缓存配置接口\n *\n * @author 渔民小镇\n * @date 2023-07-02\n */\npublic interface ExternalCmdCacheSetting {\n\n    /**\n     * 设置游戏对外服缓存默认配置\n     *\n     * @param option 配置\n     */\n    void setCmdCacheOption(CmdCacheOption option);\n\n    /**\n     * 获取游戏对外服缓存默认配置\n     *\n     * @return 配置\n     */\n    CmdCacheOption getCmdCacheOption();\n\n    /**\n     * 添加路由范围缓存，指定配置\n     * <pre>\n     *     假设添加了主路由为 1 的值。游戏对外服会将主路由为 1 下的所有子路由的数据都做缓存。\n     *\n     *     比如 1-1、1-2、1-N ，即使你没有配置这些子路由相关的缓存，也是会生效的。\n     * </pre>\n     *\n     * @param cmd            主路由\n     * @param cmdCacheOption 配置\n     */\n    void addCmd(int cmd, CmdCacheOption cmdCacheOption);\n\n    /**\n     * 添加路由范围缓存，使用默认配置\n     * <pre>\n     *     假设添加了主路由为 1 的值。游戏对外服会将主路由为 1 下的所有子路由的数据都做缓存。\n     *\n     *     比如 1-1、1-2、1-N ，即使你没有配置这些子路由相关的缓存，也是会生效的。\n     * </pre>\n     *\n     * @param cmd 主路由\n     */\n    default void addCmd(int cmd) {\n        this.addCmd(cmd, getCmdCacheOption());\n    }\n\n    /**\n     * 添加路由缓存，指定配置\n     *\n     * @param cmd            主路由\n     * @param subCmd         子路由\n     * @param cmdCacheOption 配置\n     */\n    void addCmd(int cmd, int subCmd, CmdCacheOption cmdCacheOption);\n\n    /**\n     * 添加路由缓存，使用默认配置\n     *\n     * @param cmd    主路由\n     * @param subCmd 子路由\n     */\n    default void addCmd(int cmd, int subCmd) {\n        this.addCmd(cmd, subCmd, getCmdCacheOption());\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/cache/InternalAboutCache.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook.cache;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.external.core.kit.ExternalKit;\nimport io.netty.util.Timeout;\nimport io.netty.util.TimerTask;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 框架内置的缓存默认实现类\n *\n * @author 渔民小镇\n * @date 2024-09-16\n * @since 21.17\n */\nfinal class SimpleExternalCmdCache implements ExternalCmdCache {\n\n    CmdCacheOption cmdCacheOption = CmdCacheOption.newBuilder().build();\n\n    @Override\n    public void setCmdCacheOption(CmdCacheOption option) {\n        this.cmdCacheOption = option;\n    }\n\n    @Override\n    public CmdCacheOption getCmdCacheOption() {\n        return this.cmdCacheOption;\n    }\n\n    @Override\n    public void addCmd(int cmd, CmdCacheOption option) {\n        CmdCacheRegion cmdCacheRegion = CmdCacheRegions.getCmdCacheRegion(cmd);\n        cmdCacheRegion.setRange(true);\n        cmdCacheRegion.setCmdCacheOption(option);\n    }\n\n    @Override\n    public void addCmd(int cmd, int subCmd, CmdCacheOption option) {\n        int cmdMerge = CmdKit.merge(cmd, subCmd);\n        CmdCacheRegion cmdCacheRegion = CmdCacheRegions.getCmdCacheRegion(cmd);\n        cmdCacheRegion.addCmdCache(cmdMerge, option);\n    }\n\n    @Override\n    public BarMessage getCache(BarMessage message) {\n\n        HeadMetadata headMetadata = message.getHeadMetadata();\n        CmdInfo cmdInfo = headMetadata.getCmdInfo();\n        CmdActionCache cmdCache = CmdCacheRegions.getCmdActionCache(cmdInfo);\n\n        if (Objects.isNull(cmdCache)) {\n            // 表示没有对路由做缓存配置\n            return null;\n        }\n\n        byte[] responseCacheData = cmdCache.getCacheData(message);\n        if (Objects.isNull(responseCacheData)) {\n            return null;\n        }\n\n        // 当响应的缓存数据存在时，将缓存数据设置到 data 中；可避免 new、序列化 ...等操作。\n        message.setData(responseCacheData);\n        return message;\n    }\n\n    @Override\n    public void addCacheData(ResponseMessage responseMessage) {\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        CmdInfo cmdInfo = headMetadata.getCmdInfo();\n        CmdActionCache cmdCache = CmdCacheRegions.getCmdActionCache(cmdInfo);\n\n        // 没有配置缓存，不做处理\n        if (Objects.isNull(cmdCache)) {\n            return;\n        }\n\n        cmdCache.addCacheData(responseMessage);\n    }\n}\n\n/**\n * 路由 action 缓存\n *\n * @author 渔民小镇\n * @date 2023-12-15\n */\nfinal class CmdActionCache {\n    /**\n     * 缓存数据\n     * <pre>\n     *     key : requestParam hashId。以请求时的参数 hash 后作为 key\n     *     value : cache data。对应的缓存数据\n     * </pre>\n     */\n    final Map<Integer, CacheNode> cacheDataMap = new NonBlockingHashMap<>();\n    @Getter\n    final CmdCacheOption cmdCacheOption;\n\n    CmdActionCache(CmdCacheOption cmdCacheOption) {\n        this.cmdCacheOption = cmdCacheOption;\n    }\n\n    /**\n     * 得到缓存的业务数据\n     *\n     * @param message message\n     * @return response data\n     */\n    byte[] getCacheData(BarMessage message) {\n\n        byte[] data = message.getData();\n        int cacheCondition = ExternalKit.getCacheCondition(data);\n        CacheNode cacheNode = cacheDataMap.get(cacheCondition);\n\n        if (Objects.isNull(cacheNode)) {\n            // 当没有找到缓存时，将请求的具体业务参数作为缓存条件，用于后续处理。see ResponseMessageExternalProcessor\n            HeadMetadata headMetadata = message.getHeadMetadata();\n            headMetadata.setCacheCondition(cacheCondition);\n            return null;\n        }\n\n        return cacheNode.cacheData;\n    }\n\n    void addCacheData(ResponseMessage responseMessage) {\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        int cacheCondition = headMetadata.getCacheCondition();\n        if (cacheCondition == 0) {\n            return;\n        }\n\n        int size = this.cacheDataMap.size();\n        int cacheLimit = this.cmdCacheOption.getCacheLimit();\n        if (size >= cacheLimit) {\n            // 如果缓存已经达到配置的上限，则不将数据添加到缓存中。\n            return;\n        }\n\n        // 这里的 data 有可能为 null，后续版本中可以添加一些缓存击穿相关的防范，现阶段不着急。\n        byte[] data = responseMessage.getData();\n        int expireCheckTime = (int) this.cmdCacheOption.getExpireCheckTime().getSeconds();\n        int expireTimeCount = (int) this.cmdCacheOption.getExpireTime().getSeconds();\n        // 新增缓存 Node\n        CacheNode cacheNode = new CacheNode(expireCheckTime, data);\n        cacheNode.expireTimeCount = expireTimeCount;\n\n        // 将数据添加到缓存中\n        this.cacheDataMap.put(cacheCondition, cacheNode);\n    }\n\n    void expireMonitor() {\n        if (this.cacheDataMap.isEmpty()) {\n            return;\n        }\n\n        // 如果缓存到期，移除缓存\n        this.cacheDataMap.entrySet().removeIf(entry -> entry.getValue().expire());\n    }\n\n    private static final class CacheNode {\n        final int expireCheckTime;\n        final byte[] cacheData;\n        int expireTimeCount;\n\n        CacheNode(int expireCheckTime, byte[] cacheData) {\n            this.expireCheckTime = expireCheckTime;\n            this.cacheData = cacheData;\n        }\n\n        boolean expire() {\n            expireTimeCount -= expireCheckTime;\n            return expireTimeCount <= 0;\n        }\n    }\n}\n\n/**\n * 缓存域，同一主路由下的。\n *\n * @author 渔民小镇\n * @date 2023-12-15\n */\n@Setter\nfinal class CmdCacheRegion {\n    /**\n     * 路由数据缓存对象\n     * <pre>\n     *     key : cmdMerge\n     *     value : CmdCache\n     * </pre>\n     */\n    final Map<Integer, CmdActionCache> cmdCacheMap = new NonBlockingHashMap<>();\n    /** 是否开启范围缓存 */\n    boolean range;\n    CmdCacheOption cmdCacheOption;\n\n    /**\n     * 得到 CmdMergeCache，如果不存在则表示没有做相关的缓存配置\n     *\n     * @param cmdMerge cmdMerge\n     * @return CmdMergeCache\n     */\n    CmdActionCache getCmdCache(int cmdMerge) {\n\n        var cmdActionCache = this.cmdCacheMap.get(cmdMerge);\n        if (Objects.nonNull(cmdActionCache)) {\n            return cmdActionCache;\n        }\n\n        // 如果开启了范围缓存，即使没有显示的配置，也会生成缓存对象\n        return range ? addCmdCache(cmdMerge, this.cmdCacheOption) : null;\n    }\n\n    CmdActionCache addCmdCache(int cmdMerge, CmdCacheOption cmdCacheOption) {\n\n        var cmdActionCache = this.cmdCacheMap.get(cmdMerge);\n        if (Objects.nonNull(cmdActionCache)) {\n            return cmdActionCache;\n        }\n\n        var cache = MoreKit.putIfAbsent(this.cmdCacheMap, cmdMerge, new CmdActionCache(cmdCacheOption));\n        // 缓存过期时间检测\n        extractedExpire(cache);\n        return cache;\n    }\n\n    private void extractedExpire(CmdActionCache cmdActionCache) {\n        TaskKit.newTimeout(new TimerTask() {\n            @Override\n            public void run(Timeout timeout) {\n                // 开启过期检测\n                cmdActionCache.expireMonitor();\n\n                CmdCacheOption option = cmdActionCache.getCmdCacheOption();\n                long delay = option.getExpireCheckTime().getSeconds();\n\n                TaskKit.newTimeout(this, delay, TimeUnit.SECONDS);\n            }\n        }, 3, TimeUnit.SECONDS);\n    }\n}\n\n/**\n * 缓存域管理\n *\n * @author 渔民小镇\n * @date 2023-12-15\n */\n@UtilityClass\nfinal class CmdCacheRegions {\n    /**\n     * <pre>\n     *     key : cmd\n     *     value : CmdCacheRegion\n     * </pre>\n     */\n    final Map<Integer, CmdCacheRegion> cmdCacheRegionMap = new NonBlockingHashMap<>();\n\n    CmdCacheRegion getCmdCacheRegion(int cmd) {\n\n        CmdCacheRegion cmdCacheRegion = cmdCacheRegionMap.get(cmd);\n\n        // 无锁化\n        if (Objects.isNull(cmdCacheRegion)) {\n            return MoreKit.putIfAbsent(cmdCacheRegionMap, cmd, new CmdCacheRegion());\n        }\n\n        return cmdCacheRegion;\n    }\n\n    CmdActionCache getCmdActionCache(CmdInfo cmdInfo) {\n        var cmd = cmdInfo.getCmd();\n        CmdCacheRegion cmdCacheRegion = CmdCacheRegions.getCmdCacheRegion(cmd);\n\n        var cmdMerge = cmdInfo.getCmdMerge();\n        return cmdCacheRegion.getCmdCache(cmdMerge);\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/cache/internal/DefaultExternalCmdCache.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage com.iohao.game.external.core.hook.cache.internal;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.external.core.hook.cache.CmdCacheOption;\nimport com.iohao.game.external.core.hook.cache.ExternalCmdCache;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 框架内置的缓存默认实现类\n *\n * @author 渔民小镇\n * @date 2023-12-15\n * @deprecated 请使用 {@link ExternalCmdCache#of()}\n */\n@Deprecated\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultExternalCmdCache implements ExternalCmdCache {\n    final ExternalCmdCache externalCmdCache = ExternalCmdCache.of();\n\n    @Override\n    public BarMessage getCache(BarMessage message) {\n        return this.externalCmdCache.getCache(message);\n    }\n\n    @Override\n    public void addCacheData(ResponseMessage responseMessage) {\n        this.externalCmdCache.addCacheData(responseMessage);\n    }\n\n    @Override\n    public void setCmdCacheOption(CmdCacheOption option) {\n        externalCmdCache.setCmdCacheOption(option);\n    }\n\n    @Override\n    public CmdCacheOption getCmdCacheOption() {\n        return externalCmdCache.getCmdCacheOption();\n    }\n\n    @Override\n    public void addCmd(int cmd, CmdCacheOption option) {\n        this.externalCmdCache.addCmd(cmd, option);\n    }\n\n    @Override\n    public void addCmd(int cmd, int subCmd, CmdCacheOption option) {\n        this.externalCmdCache.addCmd(cmd, subCmd, option);\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/cache/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - core - <a href=\"https://iohao.github.io/game/docs/external/cache\">游戏对外服缓存</a>\n *\n * <pre>\n *     我们对业务数据做缓存时，一般的做法是通过 Caffeine、cache2k、ehcache、JetCache 等专业的缓存库，将业务数据缓存在游戏逻辑服中，以实现对业务数据的缓存。\n *\n *     而游戏对外服缓存，可以将一些热点的业务数据缓存在游戏对外服中，玩家每次访问相关路由时，会直接从游戏对外服的内存中取数据。这样可以避免反复请求游戏逻辑服，从而达到性能的超级提升；\n *\n *     当我们把游戏对外服缓存与专业的缓存库做结合时，可以发挥更大的性能效果。因为我们可以将热点数据缓存在游戏对外服中，之后其他玩家访问热点数据时，就不需要去游戏逻辑服中取数据，而是直接在游戏对外服这一环节中就能得到数据了。\n *\n *     游戏对外服缓存的使用方式大概与路由访问权限控制差不多，如果你之前了解过这部分的内容，那么花几分钟就能上手了。\n *\n *     游戏对外服缓存对性能有着巨大的提升，主要体现在几个方面\n *     1. 当玩家访问缓存数据时，响应更快了，因为请求链更少了。\n *     2. 直接在游戏对外服中取数据，无需将请求传递到游戏逻辑服中，无需对业务数据做序列化操作。\n *     3. 避免请求传递到游戏逻辑服中，节省系统资源。\n *\n *     特点\n *     1. 零学习成本\n *     2. 可快速响应玩家请求。\n *     3. 简化了缓存的使用，即使没有在游戏逻辑服中对这些业务数据做缓存，只要在游戏对外服配置好相关的路由缓存，就能达到缓存的效果。\n *     4. 减少请求传递，同时游戏对外服缓存还可以减少请求的传递，使得业务数据在游戏对外服就能处理，而不需要经过游戏逻辑服；\n *     5. 避免序列化操作，由于路由对应的业务数据是以 byte[] 类型缓存在游戏对外服的，所以从缓存中取得的业务数据，将不再需要序列化（编码）操作了。简单点说，就是不需要将业务对象转换成 byte[] 类型了；\n *     6. 支持条件缓存，同一 action 支持不同的请求参数。\n *     7. 支持路由范围缓存配置\n * </pre>\n * for example\n * <pre>{@code\n *     // 创建框架内置的缓存实现类\n *     var externalCmdCache = ExternalCmdCache.of();\n *     // 添加全局配置中\n *     ExternalGlobalConfig.externalCmdCache = externalCmdCache;\n *\n *     // 即使不设置，框架默认也是这个配置，这里只是展示如何设置默认的缓存配置。\n *     CmdCacheOption defaultOption = CmdCacheOption.newBuilder()\n *             // 缓存过期时间，1 小时\n *             .setExpireTime(Duration.ofHours(1))\n *             // 缓存过期检测时间间隔 5 分钟\n *             .setExpireCheckTime(Duration.ofMinutes(5))\n *             // 同一个 action 的缓存数量上限设置为 256 条\n *             .setCacheLimit(256)\n *             // 构建缓存配置\n *             .build();\n *\n *     // 设置为默认的缓存配置，之后添加的路由缓存都将使用这个缓存配置\n *     externalCmdCache.setCmdCacheOption(defaultOption);\n *\n *     // 添加路由缓存 22-1，使用默认的缓存配置\n *     externalCmdCache.addCmd(CacheCmd.cmd, CacheCmd.cacheHere);\n *\n *     // 新增一个缓存配置对象，对业务做更精细的控制。\n *     CmdCacheOption optionCustom = CmdCacheOption.newBuilder()\n *             // 缓存过期时间 30 秒\n *             .setExpireTime(Duration.ofSeconds(30))\n *             // 缓存过期检测时间间隔 5 秒\n *             .setExpireCheckTime(Duration.ofSeconds(5))\n *             // 构建缓存配置\n *             .build();\n *\n *     // 添加路由缓存，使用自定义缓存配置\n *     externalCmdCache.addCmd(CacheCmd.cmd, CacheCmd.cacheCustom, optionCustom);\n *     externalCmdCache.addCmd(CacheCmd.cmd, CacheCmd.cacheList, optionCustom);\n *\n *     // 添加路由范围缓存，使用默认的缓存配置\n *     externalCmdCache.addCmd(2);\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2023-07-02\n */\npackage com.iohao.game.external.core.hook.cache;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/internal/DefaultAccessAuthenticationHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook.internal;\n\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.external.core.hook.AccessAuthenticationHook;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class DefaultAccessAuthenticationHook implements AccessAuthenticationHook {\n    /**\n     * 需要忽略的路由，可以添加到这 set 中\n     */\n    final Set<Integer> cmdMergeSet = new NonBlockingHashSet<>();\n    /**\n     * 需要忽略的主路由，可以添加到这 set 中\n     */\n    final Set<Integer> cmdSet = new NonBlockingHashSet<>();\n\n    /**\n     * 需要拒绝的路由\n     */\n    final Set<Integer> rejectionCmdMergeSet = new NonBlockingHashSet<>();\n    /**\n     * 需要拒绝的主路由\n     */\n    final Set<Integer> rejectionCmdSet = new NonBlockingHashSet<>();\n\n    /**\n     * true 表示请求业务方法需要先登录，默认不需要登录\n     */\n    @Setter\n    boolean verifyIdentity;\n\n    @Override\n    public void addIgnoreAuthCmd(int cmd, int subCmd) {\n        int cmdMerge = CmdKit.merge(cmd, subCmd);\n        this.cmdMergeSet.add(cmdMerge);\n    }\n\n    @Override\n    public void addIgnoreAuthCmd(int cmd) {\n        this.cmdSet.add(cmd);\n    }\n\n    @Override\n    public void removeIgnoreAuthCmd(int cmd, int subCmd) {\n        int cmdMerge = CmdKit.merge(cmd, subCmd);\n        this.cmdMergeSet.remove(cmdMerge);\n    }\n\n    @Override\n    public void removeIgnoreAuthCmd(int cmd) {\n        this.cmdSet.remove(cmd);\n    }\n\n    @Override\n    public boolean pass(boolean loginSuccess, int cmdMerge) {\n\n        if (!this.verifyIdentity) {\n            // 表示不需要登录，就可以访问所有的业务方法（action）\n            return true;\n        }\n\n        // 已经【登录】的玩家，可以直接访问业务方法\n        return loginSuccess\n                // 在忽略的【路由】范围内的，可以直接访问业务方法（不需要登录）\n                || this.cmdMergeSet.contains(cmdMerge)\n                // 在忽略的【主路由】范围内的，可以直接访问业务方法（不需要登录）\n                || this.cmdSet.contains(CmdKit.getCmd(cmdMerge));\n    }\n\n    @Override\n    public void addRejectionCmd(int cmd) {\n        this.rejectionCmdSet.add(cmd);\n    }\n\n    @Override\n    public void addRejectionCmd(int cmd, int subCmd) {\n        int cmdMerge = CmdKit.merge(cmd, subCmd);\n        this.rejectionCmdMergeSet.add(cmdMerge);\n    }\n\n    @Override\n    public void removeRejectCmd(int cmd, int subCmd) {\n        int cmdMerge = CmdKit.merge(cmd, subCmd);\n        this.rejectionCmdMergeSet.remove(cmdMerge);\n    }\n\n    @Override\n    public void removeRejectCmd(int cmd) {\n        this.rejectionCmdSet.remove(cmd);\n    }\n\n    @Override\n    public boolean reject(int cmdMerge) {\n        // 在拒绝访问的【路由】范围内的，不能直接访问业务方法\n        return this.rejectionCmdMergeSet.contains(cmdMerge)\n                // 在拒绝访问的【主路由】范围内的，不能直接访问业务方法\n                || this.rejectionCmdSet.contains(CmdKit.getCmd(cmdMerge));\n    }\n\n    @Override\n    public void clear() {\n        this.cmdSet.clear();\n        this.cmdMergeSet.clear();\n        this.rejectionCmdSet.clear();\n        this.rejectionCmdMergeSet.clear();\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/internal/DefaultUserHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook.internal;\n\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.hook.UserHook;\nimport com.iohao.game.external.core.session.UserSession;\nimport com.iohao.game.external.core.session.UserSessions;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 上线下线钩子实现类\n *\n * @author 渔民小镇\n * @date 2023-02-20\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class DefaultUserHook implements UserHook, UserSessionsAware {\n    UserSessions<?, ?> userSessions;\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n\n    @Override\n    public void into(UserSession userSession) {\n        long userId = userSession.getUserId();\n        log.info(\"{}:{}  userId:{} -- {}, into\"\n                , Bundle.getMessage(MessageKey.userHookInto)\n                , userSessions.countOnline()\n                , userId, userSession.getUserChannelId());\n    }\n\n    @Override\n    public void quit(UserSession userSession) {\n\n        long userId = userSession.getUserId();\n        log.info(\"{}:{}  userId:{} -- {}, quit\"\n                , Bundle.getMessage(MessageKey.userHookQuit)\n                , userSessions.countOnline()\n                , userId, userSession.getUserChannelId());\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/internal/IdleProcessSetting.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.hook.internal;\n\nimport com.iohao.game.external.core.hook.IdleHook;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-18\n */\n@Setter\n@Getter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class IdleProcessSetting {\n    /** 心跳整体时间 */\n    long idleTime = 300;\n    /** 读 - 心跳时间 */\n    long readerIdleTime = idleTime;\n    /** 写 - 心跳时间 */\n    long writerIdleTime = idleTime;\n    /** all - 心跳时间 */\n    long allIdleTime = idleTime;\n    /** 心跳时间单位 - 默认秒单位 */\n    TimeUnit timeUnit = TimeUnit.SECONDS;\n    /** true : 响应心跳给客户端 */\n    boolean pong = true;\n    /** 心跳钩子 */\n    IdleHook<?> idleHook;\n\n    /**\n     * 心跳整体时间设置包括：readerIdleTime、writerIdleTime、allIdleTime\n     *\n     * @param idleTime 整体时间\n     * @return this\n     */\n    public IdleProcessSetting setIdleTime(long idleTime) {\n        this.idleTime = idleTime;\n        this.readerIdleTime = idleTime;\n        this.writerIdleTime = idleTime;\n        this.allIdleTime = idleTime;\n        return this;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> IdleHook<T> getIdleHook() {\n        return (IdleHook<T>) idleHook;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/internal/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - core - 钩子接口相关，钩子接口的默认实现类\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.hook.internal;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/hook/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - core - 钩子接口相关，<a href=\"https://iohao.github.io/game/docs/external/idle\">心跳设置与心跳钩子</a>、<a href=\"https://iohao.github.io/game/docs/external/user_hook\">用户上线、下线的钩子</a>、<a href=\"https://iohao.github.io/game/docs/external/access_authentication\">路由访问权限的控制</a> 等。\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.hook;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/kit/ExternalKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.kit;\n\nimport com.alipay.remoting.rpc.RpcCommandType;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.common.kit.HashKit;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport com.iohao.game.external.core.message.ExternalMessageCmdCode;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@UtilityClass\npublic class ExternalKit {\n\n    /**\n     * 创建请求消息\n     *\n     * @param cmdMerge 路由 {@link CmdKit#merge(int, int)}\n     * @param idHash   当前游戏对外服的 idHash\n     * @return 请求消息\n     */\n    public RequestMessage createRequestMessage(int cmdMerge, int idHash) {\n        return createRequestMessage(cmdMerge, idHash, null);\n    }\n\n    /**\n     * 创建请求消息\n     *\n     * @param cmdMerge 路由 {@link CmdKit#merge(int, int)}\n     * @param idHash   当前游戏对外服的 idHash\n     * @param data     业务数据 byte[]\n     * @return 请求消息\n     */\n    public RequestMessage createRequestMessage(int cmdMerge, int idHash, byte[] data) {\n        // 元信息\n        HeadMetadata headMetadata = new HeadMetadata()\n                .setCmdMerge(cmdMerge)\n                .setRpcCommandType(RpcCommandType.REQUEST_ONEWAY)\n                .setSourceClientId(idHash);\n\n        // 请求\n        RequestMessage requestMessage = new RequestMessage();\n        requestMessage.setHeadMetadata(headMetadata);\n\n        requestMessage.setData(data);\n\n        return requestMessage;\n    }\n\n    public ExternalMessage createExternalMessage() {\n        // 游戏框架内置的协议， 与游戏前端相互通讯的协议\n        ExternalMessage externalMessage = new ExternalMessage();\n        // 请求命令类型: 0 心跳，1 业务\n        externalMessage.setCmdCode(ExternalMessageCmdCode.biz);\n        // 协议开关，用于一些协议级别的开关控制，比如 安全加密校验等。 : 0 不校验\n        externalMessage.setProtocolSwitch(ExternalGlobalConfig.protocolSwitch);\n        return externalMessage;\n    }\n\n    public ExternalMessage createExternalMessage(CmdInfo cmdInfo, byte[] data) {\n        return createExternalMessage(cmdInfo.getCmd(), cmdInfo.getSubCmd(), data);\n    }\n\n    public ExternalMessage createExternalMessage(CmdInfo cmdInfo, Object object) {\n        return createExternalMessage(cmdInfo.getCmd(), cmdInfo.getSubCmd(), object);\n    }\n\n    public ExternalMessage createExternalMessage(CmdInfo cmdInfo) {\n        ExternalMessage externalMessage = ExternalKit.createExternalMessage();\n        externalMessage.setCmdMerge(cmdInfo.getCmdMerge());\n        return externalMessage;\n    }\n\n    public ExternalMessage createExternalMessage(int cmd, int subCmd) {\n        ExternalMessage externalMessage = ExternalKit.createExternalMessage();\n        externalMessage.setCmdMerge(cmd, subCmd);\n        return externalMessage;\n    }\n\n    public ExternalMessage createExternalMessage(int cmd, int subCmd, Object object) {\n        byte[] data = null;\n\n        if (object != null) {\n            data = DataCodecKit.encode(object);\n        }\n\n        return ExternalKit.createExternalMessage(cmd, subCmd, data);\n    }\n\n    public ExternalMessage createExternalMessage(int cmd, int subCmd, byte[] data) {\n        // 游戏框架内置的协议， 与游戏前端相互通讯的协议\n        ExternalMessage externalMessage = ExternalKit.createExternalMessage(cmd, subCmd);\n\n        // 业务数据\n        externalMessage.setData(data);\n\n        return externalMessage;\n    }\n\n    /**\n     * byte[] 转 hash\n     * <pre>\n     *     缓存查询条件: 由请求参数计算出一个 hash 值。\n     *     同一 action 条件参数的 hash 值碰撞的几率不是很大。\n     *\n     *     当条件参数不存在时，那么就是无参 action，使用 1 来表示。\n     * </pre>\n     *\n     * @param data bytes\n     * @return hash\n     */\n    public int getCacheCondition(byte[] data) {\n        return Objects.nonNull(data) ? HashKit.hash32(data) : 1;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/kit/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - core - 工具包\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.kit;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/message/DefaultExternalCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.message;\n\nimport com.alipay.remoting.rpc.RpcCommandType;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\n\n/**\n * 默认的游戏对外服协议编解码\n * <pre>\n *     使用默认的游戏对外服协议 {@link ExternalMessage}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-15\n * @see ExternalMessage\n */\n@SuppressWarnings(\"unchecked\")\npublic final class DefaultExternalCodec implements ExternalCodec {\n    @Override\n    public <T> T convertExternalMessage(BarMessage message) {\n        // 游戏框架内置的协议， 与游戏前端相互通讯的协议\n        ExternalMessage externalMessage = createExternalMessage();\n        HeadMetadata headMetadata = message.getHeadMetadata();\n        // 路由\n        externalMessage.setCmdMerge(headMetadata.getCmdMerge());\n        // 业务数据\n        externalMessage.setData(message.getData());\n        // 状态码\n        externalMessage.setResponseStatus(message.getResponseStatus());\n        // 验证信息（异常消息）\n        externalMessage.setValidMsg(message.getValidatorMsg());\n        // 消息标记号；由前端请求时设置，服务器响应时会携带上\n        externalMessage.setMsgId(headMetadata.getMsgId());\n        // 开发者自定义数据\n        externalMessage.setCustomData(headMetadata.getCustomData());\n        // 请求命令类型: 0 心跳，1 业务\n        externalMessage.setCmdCode(headMetadata.getCmdCode());\n        externalMessage.setOther(headMetadata.getInetSocketAddress());\n\n        return (T) externalMessage;\n    }\n\n    @Override\n    public <T> T createExternalMessage() {\n        // 游戏框架内置的协议， 与游戏前端相互通讯的协议\n        ExternalMessage externalMessage = new ExternalMessage();\n        // 请求命令类型: 0 心跳，1 业务\n        externalMessage.setCmdCode(ExternalMessageCmdCode.biz);\n        // 协议开关，用于一些协议级别的开关控制，比如 安全加密校验等。 : 0 不校验\n        externalMessage.setProtocolSwitch(ExternalGlobalConfig.protocolSwitch);\n        return (T) externalMessage;\n    }\n\n    @Override\n    public RequestMessage convertRequest(Object externalMsg) {\n        ExternalMessage externalMessage = (ExternalMessage) externalMsg;\n        int cmdMerge = externalMessage.getCmdMerge();\n\n        // 元信息\n        HeadMetadata headMetadata = new HeadMetadata()\n                .setCmdMerge(cmdMerge)\n                .setRpcCommandType(RpcCommandType.REQUEST_ONEWAY)\n                .setMsgId(externalMessage.getMsgId())\n                .setCmdCode(externalMessage.getCmdCode())\n                .setCustomData(externalMessage.getCustomData());\n\n        byte[] data = externalMessage.getData();\n\n        // 请求\n        RequestMessage requestMessage = new RequestMessage();\n        requestMessage.setResponseStatus(externalMessage.getResponseStatus());\n        requestMessage.setValidatorMsg(externalMessage.getValidMsg());\n        requestMessage.setHeadMetadata(headMetadata);\n        requestMessage.setData(data);\n\n        return requestMessage;\n    }\n}"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/message/ExternalCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.message;\n\nimport com.alipay.remoting.rpc.RpcCommandType;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\n\n/**\n * 游戏对外服协议编解码\n * <pre>\n *     开发者可自定义游戏对外服协议\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-15\n */\npublic interface ExternalCodec {\n    /**\n     * 创建 ResponseMessage\n     *\n     * @return ResponseMessage\n     */\n    default ResponseMessage createResponse() {\n        var headMetadata = new HeadMetadata();\n        // 请求命令类型: 0 心跳，1 业务\n        headMetadata.setCmdCode(ExternalMessageCmdCode.biz);\n        // 协议开关，用于一些协议级别的开关控制，比如 安全加密校验等。 : 0 不校验\n        headMetadata.setProtocolSwitch(ExternalGlobalConfig.protocolSwitch);\n\n        var responseMessage = new ResponseMessage();\n        responseMessage.setHeadMetadata(headMetadata);\n        return responseMessage;\n    }\n\n    default RequestMessage createRequest() {\n        var headMetadata = new HeadMetadata();\n        // 请求命令类型: 0 心跳，1 业务\n        headMetadata.setCmdCode(ExternalMessageCmdCode.biz);\n        // 协议开关，用于一些协议级别的开关控制，比如 安全加密校验等。 : 0 不校验\n        headMetadata.setProtocolSwitch(ExternalGlobalConfig.protocolSwitch);\n        headMetadata.setRpcCommandType(RpcCommandType.REQUEST_ONEWAY);\n\n        var requestMessage = new RequestMessage();\n        requestMessage.setHeadMetadata(headMetadata);\n        return requestMessage;\n    }\n\n    /**\n     * 将 BarMessage 转为游戏对外服协议\n     *\n     * @param message BarMessage\n     * @param <T>     t\n     * @return 游戏对外服协议\n     */\n    <T> T convertExternalMessage(BarMessage message);\n\n    /**\n     * 创建游戏对外服协议\n     *\n     * @param <T> t\n     * @return 游戏对外服协议\n     */\n    <T> T createExternalMessage();\n\n    /**\n     * 将游戏对外服协议转为 RequestMessage\n     *\n     * @param externalMessage 游戏对外服协议\n     * @return RequestMessage\n     */\n    RequestMessage convertRequest(Object externalMessage);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/message/ExternalCodecKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.message;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.message.BroadcastMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.common.consts.CommonConst;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.external.core.session.UserSessions;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Collection;\n\n/**\n * 游戏对外服协议编解码工具\n *\n * @author 渔民小镇\n * @date 2023-12-15\n */\n@UtilityClass\npublic class ExternalCodecKit {\n    public ExternalCodec externalCodec = new DefaultExternalCodec();\n\n    public BarMessage createIdleMessage() {\n        BarMessage response = externalCodec.createResponse();\n        HeadMetadata headMetadata = response.getHeadMetadata();\n\n        // 请求命令类型: 心跳\n        headMetadata.setCmdCode(ExternalMessageCmdCode.idle);\n        return response;\n    }\n\n    public BarMessage createErrorIdleMessage(ActionErrorEnum idleErrorCode) {\n        BarMessage response = createIdleMessage();\n        // 错误码\n        response.setResponseStatus(idleErrorCode.getCode());\n        // 错误消息\n        response.setValidatorMsg(idleErrorCode.getMsg());\n        return response;\n    }\n\n    public RequestMessage createRequest() {\n        return externalCodec.createRequest();\n    }\n\n    public RequestMessage createRequest(CmdInfo cmdInfo) {\n        RequestMessage request = createRequest();\n        request.getHeadMetadata().setCmdInfo(cmdInfo);\n        return request;\n    }\n\n    public ResponseMessage createResponse() {\n        return externalCodec.createResponse();\n    }\n\n    public <T> T convertExternalMessage(BarMessage responseMessage) {\n        return externalCodec.convertExternalMessage(responseMessage);\n    }\n\n    public <T> T createExternalMessage() {\n        return externalCodec.createExternalMessage();\n    }\n\n    public RequestMessage convertRequestMessage(Object externalMessage) {\n        return externalCodec.convertRequest(externalMessage);\n    }\n\n    public void employError(BarMessage message, MsgExceptionInfo exceptionInfo) {\n        message.setResponseStatus(exceptionInfo.getCode());\n        message.setValidatorMsg(exceptionInfo.getMsg());\n        message.setData(CommonConst.emptyBytes);\n    }\n\n    public void broadcast(BroadcastMessage message, UserSessions<?, ?> userSessions) {\n        ResponseMessage responseMessage = message.getResponseMessage();\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        headMetadata.setCmdCode(ExternalMessageCmdCode.biz);\n\n        // 推送消息给全服真实用户\n        if (message.isBroadcastAll()) {\n            userSessions.broadcast(responseMessage);\n            return;\n        }\n\n        // 推送消息给指定的真实用户列表\n        Collection<Long> userIdList = message.getUserIdList();\n        if (CollKit.notEmpty(userIdList)) {\n            userSessions.ifPresent(userIdList, userSession -> userSession.writeAndFlush(responseMessage));\n            return;\n        }\n\n        // 推送消息给单个真实用户\n        long userId = headMetadata.getUserId();\n\n        userSessions.ifPresent(userId, userSession -> userSession.writeAndFlush(responseMessage));\n    }\n\n    public void employ(BarMessage message, BrokerClient brokerClient) {\n        // 设置当前逻辑服 id\n        BrokerClientModuleMessage moduleMessage = brokerClient.getBrokerClientModuleMessage();\n        int idHash = moduleMessage.getIdHash();\n        HeadMetadata headMetadata = message.getHeadMetadata();\n        headMetadata.setSourceClientId(idHash);\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/message/ExternalMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.message;\n\nimport com.baidu.bjf.remoting.protobuf.FieldType;\nimport com.baidu.bjf.remoting.protobuf.annotation.Ignore;\nimport com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Arrays;\n\n/**\n * 游戏对外服协议\n * <pre>\n *     参考 <a href=\"https://iohao.github.io/game/docs/manual_high/external_message\">游戏对外服的协议 - 文档</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\n@Getter\n@Setter\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class ExternalMessage {\n    /** 请求命令类型: 0 心跳，1 业务 */\n    @Protobuf(fieldType = FieldType.INT32, order = 1)\n    int cmdCode;\n    /** 协议开关，用于一些协议级别的开关控制，比如 安全加密校验等。 : 0 不校验 */\n    @Protobuf(fieldType = FieldType.INT32, order = 2)\n    int protocolSwitch;\n    /** 业务路由（高16为主, 低16为子） */\n    @Protobuf(fieldType = FieldType.INT32, order = 3)\n    int cmdMerge;\n\n    /**\n     * 响应码。\n     * <pre>\n     *     从字段精简的角度，我们不可能每次响应都带上完整的异常信息给客户端排查问题，\n     *     因此，我们会定义一些响应码，通过编号进行网络传输，方便客户端定位问题。\n     *\n     *     0:成功\n     *     !=0: 表示有错误\n     * </pre>\n     */\n    @Protobuf(fieldType = FieldType.SINT32, order = 4)\n    int responseStatus;\n    /** 验证信息（错误消息、异常消息） */\n    @Protobuf(fieldType = FieldType.STRING, order = 5)\n    String validMsg;\n    /** 业务数据 */\n    @Protobuf(fieldType = FieldType.BYTES, order = 6)\n    byte[] data;\n    /** 消息标记号；由前端请求时设置，服务器响应时会携带上 */\n    @Protobuf(fieldType = FieldType.INT32, order = 7)\n    int msgId;\n    /** 预留 */\n    @Ignore\n    transient Object other;\n    /**\n     * 实验性：预留给开发者的字段\n     * <pre>\n     *     具备透传，服务器响应时会携带上\n     *     需要注意的是，目前该字段是不会序列化到客户端的\n     * </pre>\n     */\n    @Ignore\n    transient byte[] customData;\n\n    /**\n     * 业务数据\n     *\n     * @param data 业务数据\n     */\n    public void setData(byte[] data) {\n        if (data != null) {\n            this.data = data;\n        }\n    }\n\n    /**\n     * 业务路由\n     *\n     * @param cmd    主路由\n     * @param subCmd 子路由\n     */\n    public void setCmdMerge(int cmd, int subCmd) {\n        this.cmdMerge = CmdKit.merge(cmd, subCmd);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getOther() {\n        return (T) other;\n    }\n\n    @Override\n    public String toString() {\n        return \"ExternalMessage{\" +\n                \"cmdCode=\" + cmdCode +\n                \", protocolSwitch=\" + protocolSwitch +\n                \", cmdMerge=\" + cmdMerge +\n                \", responseStatus=\" + responseStatus +\n                \", validMsg='\" + validMsg + '\\'' +\n                \", msgId=\" + msgId +\n                \", data=\" + Arrays.toString(data) +\n                '}';\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/message/ExternalMessageCmdCode.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.message;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic interface ExternalMessageCmdCode {\n    /** 请求命令类型: 0 心跳 */\n    int idle = 0;\n    /** 请求命令类型: 1 业务 */\n    int biz = 1;\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/message/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - core - <a href=\"https://iohao.github.io/game/docs/manual_high/external_message\">对外服的协议说明</a>、游戏对外服协议编解码、自定义统一的交互协议\n * <p>\n * ExternalMessage\n * <pre>\n *     ExternalMessage 是统一交互协议，也称为游戏对外服协议；其主要作用是与游戏客户端交互的统一协议。\n *\n *     ExternalMessage 包含了\n *     1. 请求命令类型: 0 心跳，1 业务\n *     2. 业务路由（高16为主, 低16为子）\n *     3. 响应码: 0:成功, 其他为有错误\n *     4. 验证信息（错误消息、异常消息），通常情况下 responseStatus == -1001 时， 会有值\n *     5. 业务请求数据\n *     6. 消息标记号；由前端请求时设置，服务器响应时会携带上；（类似透传参数）\n * </pre>\n * <p>\n * 自定义统一的交互协议\n * <pre>\n *     ExternalMessage 是玩家与游戏服务器交互的对外统一协议。玩家（游戏客户端）在发起请求时，默认情况下是通过 ExternalMessage 来进行交互的。\n *\n *     如果没有特殊情况，建议使用框架默认提供的 ExternalMessage；因为没有使用到的字段 Protocol Buffer 会压缩数据，用多少占多少。\n *\n *     ExternalMessage 是框架提供的一种与外部交互的统一协议，也是默认推荐的方式。\n *     注意，这里说的是默认推荐方式，并不是唯一方式，开发者可以自定义这部分内容的；\n *     也就是说，可以不使用 ExternalMessage 与外部联系，而是使用一种自定义的统一协议。\n *\n *     比如你正打算开发一个物联网相关的、或者说其他的项目；想简化对外的统一协议，协议内容只需要路由与业务对象就足够了。\n *     此时，我们可以通过重写 {@link com.iohao.game.external.core.message.ExternalCodec} 接口来实现自定义协议。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.message;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/micro/MicroBootstrap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.micro;\n\nimport com.iohao.game.external.core.ExternalCoreSetting;\n\n/**\n * 与真实玩家连接的服务器\n *\n * @author 渔民小镇\n * @date 2023-05-28\n */\npublic interface MicroBootstrap {\n    /**\n     * 启动与真实玩家连接的服务器\n     */\n    void startup();\n\n    /**\n     * 设置 ExternalCoreSetting\n     *\n     * @param setting setting\n     */\n    void setExternalCoreSetting(ExternalCoreSetting setting);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/micro/MicroBootstrapFlow.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.micro;\n\n/**\n * 与真实玩家连接服务器的启动流程；\n * <pre>\n *     开发者可通过此接口对服务器做编排，编排分为：构建时、新建连接时两种。\n *\n *     也可以选择性的重写流程方法，来定制符合自身项目的业务。\n *     框架提供了 TCP、WebSocket、UDP 的实现\n * </pre>\n * <p>\n * 接口方法执行顺序为\n * <pre>\n *     1 【构建时】的执行流程，createFlow 内调用了 option、channelInitializer 方法\n *         1.1 option\n *         1.2 channelInitializer\n *\n *     2 【新建连接时】的执行流程，pipelineFlow 内调用了 pipelineCodec、pipelineIdle、pipelineCustom\n *         2.1 pipelineCodec\n *         2.2 pipelineIdle\n *         2.3 pipelineCustom\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-05-28\n */\npublic interface MicroBootstrapFlow<Bootstrap> {\n    /**\n     * 构建时的执行流程\n     *\n     * @param bootstrap 服务器\n     */\n    default void createFlow(Bootstrap bootstrap) {\n        // 开发者可以选择性的重写流程方法，来定制符合自身项目的业务\n        this.option(bootstrap);\n        this.channelInitializer(bootstrap);\n    }\n\n    /**\n     * 给服务器做一些 option 设置\n     * <pre>\n     *     构建时，此时服务器还没启动\n     * </pre>\n     *\n     * @param bootstrap 服务器\n     */\n    void option(Bootstrap bootstrap);\n\n    /**\n     * 给服务器做一些业务编排\n     * <pre>\n     *     构建时，此时服务器还没启动\n     * </pre>\n     *\n     * @param bootstrap 服务器\n     */\n    void channelInitializer(Bootstrap bootstrap);\n\n    /**\n     * 新建连接时的执行流程\n     *\n     * <pre>\n     *     通常情况下，我们可以将 ChannelInitializer 内的实现划分为三部分\n     *     1. pipelineCodec：编解码\n     *     2. pipelineIdle：心跳相关\n     *     3. pipelineCustom：自定义的业务编排 （大部分情况下只需要重写 pipelineCustom 就可以达到很强的扩展了）\n     * </pre>\n     *\n     * @param pipelineContext context\n     */\n    default void pipelineFlow(PipelineContext pipelineContext) {\n        // 编解码\n        pipelineCodec(pipelineContext);\n        // 心跳相关\n        pipelineIdle(pipelineContext);\n        /*\n         * 自定义的业务编排 pipeline\n         * 开发者可以单独重写这个接口方法，达到自定义编排 Handler，\n         * 这样可以保留编解码、心跳相关的。\n         *\n         * 开发者可以选择性的重写此方法，来做符合业务的 handler 编排\n         */\n        pipelineCustom(pipelineContext);\n    }\n\n    /**\n     * 编解码相关的\n     * <pre>\n     *     新建连接时，服务器已经启动，每次有新连接进来时，会触发。\n     * </pre>\n     *\n     * @param context PipelineContext\n     */\n    void pipelineCodec(PipelineContext context);\n\n    /**\n     * 心跳相关的\n     * <pre>\n     *     新建连接时，服务器已经启动，每次有新连接进来时，会触发。\n     * </pre>\n     *\n     * @param context PipelineContext\n     */\n    void pipelineIdle(PipelineContext context);\n\n    /**\n     * 自定义的业务编排（给服务器做一些业务编排）\n     * <pre>\n     *     新建连接时，服务器已经启动，每次有新连接进来时，会触发。\n     * </pre>\n     *\n     * @param context PipelineContext\n     */\n    void pipelineCustom(PipelineContext context);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/micro/PipelineContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.micro;\n\nimport java.util.Objects;\n\n/**\n * Pipeline 上下文\n *\n * @author 渔民小镇\n * @date 2023-02-19\n */\npublic interface PipelineContext {\n    /**\n     * 把处理器添加到第一个位置\n     *\n     * @param handler 处理器\n     */\n    default void addFirst(Object handler) {\n        Objects.requireNonNull(handler);\n        String simpleName = handler.getClass().getSimpleName();\n        this.addFirst(simpleName, handler);\n    }\n\n    /**\n     * 把处理器添加到第一个位置\n     *\n     * @param name    处理器的名称\n     * @param handler 处理器\n     */\n    void addFirst(String name, Object handler);\n\n    /**\n     * 把处理器添加到最后的位置\n     *\n     * @param handler 处理器\n     */\n    default void addLast(Object handler) {\n        Objects.requireNonNull(handler);\n        String simpleName = handler.getClass().getSimpleName();\n        this.addLast(simpleName, handler);\n    }\n\n    /**\n     * 把处理器添加到最后的位置\n     *\n     * @param name    处理器的名称\n     * @param handler 处理器\n     */\n    void addLast(String name, Object handler);\n\n    /**\n     * 移除指定处理器\n     *\n     * @param name 处理器的名称\n     */\n    void remove(String name);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/micro/join/ExternalJoinSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.micro.join;\n\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\n\n/**\n * 游戏对外服连接方式选择器\n * <pre>\n *     连接方式：tcp、websocket、udp、kcp\n *\n *     Selector 作用是根据当前的连接方式（实现类），初始化相关实现类的属性和编解码\n *         1. getCodecPipeline：编解码相关的 Pipeline\n *         2. defaultSetting：相关连接方式的一些默认设置\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-05-29\n */\npublic interface ExternalJoinSelector {\n    /**\n     * 连接方式\n     *\n     * @return 连接方式\n     */\n    ExternalJoinEnum getExternalJoinEnum();\n\n    /**\n     * 相关连接方式的一些默认设置\n     *\n     * @param coreSetting coreSetting\n     */\n    void defaultSetting(ExternalCoreSetting coreSetting);\n}"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/micro/join/ExternalJoinSelectors.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.micro.join;\n\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.EnumMap;\n\n/**\n * 连接方式 : ExternalJoinSelector\n * <pre>\n *     工厂方法\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-05-29\n */\n@UtilityClass\npublic final class ExternalJoinSelectors {\n    final EnumMap<ExternalJoinEnum, ExternalJoinSelector> map = new EnumMap<>(ExternalJoinEnum.class);\n\n    public void putIfAbsent(ExternalJoinSelector joinSelector) {\n        putIfAbsent(joinSelector.getExternalJoinEnum(), joinSelector);\n    }\n\n    public void putIfAbsent(ExternalJoinEnum joinEnum, ExternalJoinSelector joinSelector) {\n        map.putIfAbsent(joinEnum, joinSelector);\n    }\n\n    /**\n     * 根据连接方式得到对应的 ExternalJoinSelector\n     *\n     * @param joinEnum 连接方式\n     * @return ExternalJoinSelector\n     */\n    public ExternalJoinSelector getExternalJoinSelector(ExternalJoinEnum joinEnum) {\n        if (!map.containsKey(joinEnum)) {\n            // 没有对应的实现类\n            ThrowKit.ofRuntimeException(joinEnum + \" has no implementation class\");\n        }\n\n        return map.get(joinEnum);\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/micro/join/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - 连接方式 TCP、UDP、WS 的支持\n *\n * <pre>\n *     目前，ioGame 框架已经提供了 TCP、WebSocket、UDP 的实现，其支持了连接方式的灵活切换。\n *     例如，可以将 TCP、WebSocket、UDP 连接方式与业务代码进行无缝衔接，让开发者用一套业务代码，无需任何改动，同时支持多种通信协议。\n *\n *     虽然 KCP 的支持尚未实现，但是它已经被列入计划中。同样是可以通过简单的更改实现将 TCP、WebSocket、UDP 的连接方式切换成 KCP 连接方式，而这对于已有的项目而言也非常方便。\n *\n *     扩展连接方式：MicroBootstrap 在扩展上也是简单的，游戏对外服在扩展无连接的 UDP 时，只用了 400+ 行 java 代码就完成了与 TCP、WebSocket 相兼容的扩展（包括路由权限、心跳、UserSession 管理...等）。这意味着，将来我们实现 KCP 的扩展时，也是简单的。\n *\n *     连接方式的切换对业务代码没有任何影响，无需做出任何改动即可实现连接方式的更改。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.micro.join;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/micro/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - 负责与外部通信，与真实用户（玩家）建立连接\n *\n * <pre>\n *     MicroBootstrap 接口\n *     描述：与真实玩家连接的服务器，服务器的创建由 MicroBootstrap 完成，实际上 ExternalCore 是一个类似代理类的角色。MicroBootstrap 帮助开发者屏蔽连接方式的细节，如 TCP、WebSocket、UDP 等。\n *     职责：与真实玩家连接的【真实】服务器\n *\n *     MicroBootstrapFlow 接口\n *     描述：与真实玩家连接【真实】服务器的启动流程，专为 MicroBootstrap 服务。开发者可通过此接口对服务器做编排，编排分为：构建时、新建连接时两种。框架提供了 TCP、WebSocket、UDP 的实现；开发者可以选择性的重写流程方法，来定制符合自身项目的业务。\n *     职责：业务编排，也是开发者在扩展时接触最多的一个接口。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.micro;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * <a href=\"https://iohao.github.io/game/docs/overall/external_intro\">游戏对外服</a> - core\n * <p>\n * 游戏对外服的职责\n * <pre>\n *     1. 保持与用户（玩家）长的连接\n *     2. 帮助开发者屏蔽通信细节、与连接方式的细节\n *     3. 连接方式支持：WebSocket、TCP、UDP\n *     4. 将用户（玩家）请求转发到游戏网关\n *     5. 可动态增减扩展机器\n *     6. 功能扩展，如：路由存在检测、路由权限、UserSession 管理、心跳，及后续要提供但还未提供的熔断、限流、降载、用户流量统计等功能。\n * </pre>\n * <p>\n * 扩展场景\n * <pre>\n *     游戏对外服主要负责与用户（玩家）的连接。假设一台硬件支持最多建立 5000 个用户连接，当用户量达到 7000 人时，我们可以增加一个游戏对外服来进行流量控制和减压。\n *\n *     由于游戏对外服的扩展性和灵活性，可以支持同时在线玩家从几千人到数千万人不等。\n *     这是因为，通过增加游戏对外服的数量，可以有效地进行连接的负载均衡和流量控制，使得系统能够更好地承受高并发的压力。\n * </pre>\n * <p>\n * 连接方式的切换、支持、扩展\n * <pre>\n *     ioGame 已提供了 TCP、WebSocket、UDP 连接方式的支持，并提供了灵活的方式来实现连接方式的切换。\n *     可以将 TCP、WebSocket、UDP 连接方式与业务代码进行无缝衔接。开发者可以用一套业务代码，无需任何改动，同时支持多种通信协议。\n *\n *     如果想要切换到不同的连接方式，只需要更改相应的枚举即可，非常简单。\n *     在不使用 ioGame 时，将连接方式从 TCP 改为 WebSocket 或 UDP 等，需要进行大量的调整和改动，但在 ioGame 中，实现这些转换是简单的。\n *     此外，除了能轻松切换各种连接方式外，还能同时支持多种连接方式，并使它们在同一应用程序中共存。\n *\n *     连接方式是可扩展的，而且扩展也简单，这意味着之后如果支持了 KCP，那么将已有项目的连接方式，如 TCP、WebSocket、UDP 切换成 KCP 也是简单的。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-04-28\n */\npackage com.iohao.game.external.core;"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/session/UserChannelId.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.session;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic record UserChannelId(String channelId) {\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof UserChannelId that)) {\n            return false;\n        }\n\n        return Objects.equals(channelId, that.channelId);\n    }\n\n    @Override\n    public int hashCode() {\n        return channelId.hashCode();\n    }\n}"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/session/UserSession.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.session;\n\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.common.kit.attr.AttrOptionDynamic;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\n\n/**\n * UserSession interface\n * <p>\n * UserSession 接口， 对应的动态属性接口 {@link UserSessionOption}\n *\n * @author 渔民小镇\n * @date 2023-02-18\n * @see UserSessionOption UserSessionOption for dynamic properties\n */\npublic interface UserSession extends AttrOptionDynamic {\n    /**\n     * active\n     *\n     * @return true active\n     */\n    boolean isActive();\n\n    /**\n     * 设置当前用户（玩家）的 id\n     * <pre>\n     *     当设置好玩家 id ，也表示着已经身份验证了（表示登录过了）。\n     * </pre>\n     *\n     * @param userId userId\n     */\n    void setUserId(long userId);\n\n    /**\n     * 当前用户（玩家）的 id\n     *\n     * @return 当前用户（玩家）的 id\n     */\n    long getUserId();\n\n    /**\n     * authVerified\n     * <p>\n     * 是否进行身份验证\n     *\n     * @return true: logged in. （true 已经身份验证了，表示登录过了。）\n     */\n    boolean isVerifyIdentity();\n\n    /**\n     * UserSessionState of current player\n     *\n     * @return State\n     */\n    UserSessionState getState();\n\n    /**\n     * UserChannelId of current player\n     * <p>\n     * 当前用户（玩家）的 UserChannelId\n     *\n     * @return UserChannelId\n     */\n    UserChannelId getUserChannelId();\n\n    /**\n     * Adds user info to request.\n     * <p>\n     * Developers can extend data via HeadMetadata.setAttachmentData(byte[]), which will be forwarded to the logic server.\n     * <p>\n     * 给请求消息加上一些 user 自身的数据\n     * <p>\n     * 如果开发者要扩展数据，可通过 {@link HeadMetadata#setAttachmentData(byte[])} 字段来扩展，这些数据可以传递到逻辑服\n     *\n     * @param requestMessage requestMessage\n     */\n    void employ(BarMessage requestMessage);\n\n    /**\n     * Attach user data to HeadMetadata\n     * <p>\n     * 给 HeadMetadata 加上一些 user 自身的数据\n     *\n     * @param headMetadata HeadMetadata\n     */\n    void employ(HeadMetadata headMetadata);\n\n    /**\n     * writeAndFlush\n     *\n     * @param message message\n     * @return ChannelFuture\n     */\n    <T> T writeAndFlush(Object message);\n\n    /**\n     * Get player IP\n     *\n     * @return 玩家 ip\n     */\n    String getIp();\n\n    /**\n     * Creates RequestMessage with user's own info.\n     * <p>\n     * 创建 RequestMessage，内部会将 User 自身的相关信息设置到 RequestMessage 中。\n     *\n     * @param cmdInfo cmdInfo\n     * @return RequestMessage\n     * @since 21.15\n     */\n    default RequestMessage ofRequestMessage(CmdInfo cmdInfo) {\n        RequestMessage request = ExternalCodecKit.createRequest();\n        HeadMetadata headMetadata = request.getHeadMetadata();\n        headMetadata.setCmdInfo(cmdInfo);\n        // 给请求消息加上一些 user 自身的数据\n        this.employ(headMetadata);\n\n        return request;\n    }\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/session/UserSessionOption.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.session;\n\nimport com.iohao.game.common.kit.attr.AttrOption;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\n\nimport java.util.Set;\n\n/**\n * UserSession 的动态属性名\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic interface UserSessionOption {\n    /** false : 没有进行身份验证 */\n    AttrOption<Boolean> verifyIdentity = AttrOption.valueOf(\"verifyIdentity\");\n\n    /** 元信息 */\n    AttrOption<byte[]> attachment = AttrOption.valueOf(\"attachment\");\n\n    /**\n     * 玩家绑定的多个游戏逻辑服\n     * <pre>\n     *     所有与该游戏逻辑服相关的请求都将被分配给已绑定的游戏逻辑服处理。\n     *     即使启动了多个同类型的游戏逻辑服，该请求仍将被分配给已绑定的游戏逻辑服处理。\n     *\n     *     see {@link com.iohao.game.common.kit.HashKit#hash32(String)}\n     * </pre>\n     */\n    AttrOption<Set<Integer>> bindingLogicServerIdSet = AttrOption.valueOf(\"bindingLogicServerIdSet\");\n    /**\n     * 玩家绑定的多个游戏逻辑服\n     * <pre>\n     *     数据来源于 bindingLogicServerIdSet\n     *     在传输时使用基础类型数组要比 Set 性能更高\n     * </pre>\n     */\n    AttrOption<int[]> bindingLogicServerIdArray = AttrOption.valueOf(\"bindingLogicServerIdArray\");\n    /** 连接方式 */\n    AttrOption<ExternalJoinEnum> externalJoin = AttrOption.valueOf(\"externalJoin\");\n    /** 玩家真实 ip */\n    AttrOption<String> realIp = AttrOption.valueOf(\"realIp\", \"\");\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/session/UserSessionState.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.session;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic enum UserSessionState {\n    /** 活跃的 */\n    ACTIVE,\n    /** 死的 */\n    DEAD;\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/session/UserSessions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.session;\n\nimport com.iohao.game.common.kit.attr.AttrOptionDynamic;\nimport com.iohao.game.external.core.hook.UserHook;\n\nimport java.util.Collection;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * UserSession 管理器\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic interface UserSessions<SessionContext, Session extends UserSession> extends AttrOptionDynamic {\n\n    /**\n     * 加入到 session 管理\n     *\n     * @param sessionContext sessionContext\n     */\n    Session add(SessionContext sessionContext);\n\n    /**\n     * 获取 UserSession\n     *\n     * @param sessionContext sessionContext\n     * @return SessionContext\n     */\n    Session getUserSession(SessionContext sessionContext);\n\n    /**\n     * 获取 UserSession\n     *\n     * @param userId userId\n     * @return UserSession\n     */\n    Session getUserSession(long userId);\n\n    /**\n     * getUserSession\n     *\n     * @param userChannelId userChannelId\n     * @return userSession\n     */\n    Session getUserSession(UserChannelId userChannelId);\n\n    /**\n     * 如果 UserSession 存在，则使用该值执行给定操作，否则不执行任何操作。\n     *\n     * @param userId   userId\n     * @param consumer 如果 UserSession 存在，则要执行的动作\n     */\n    default void ifPresent(long userId, Consumer<Session> consumer) {\n        Session userSession = this.getUserSession(userId);\n        if (Objects.nonNull(userSession)) {\n            consumer.accept(userSession);\n        }\n    }\n\n    /**\n     * 如果 UserSession 存在，则使用该值执行给定操作，否则不执行任何操作。\n     *\n     * @param userIdList userIdList 不能为 null\n     * @param consumer   如果 UserSession 存在，则要执行的动作\n     */\n    default void ifPresent(Collection<Long> userIdList, Consumer<Session> consumer) {\n        // 不做 null 判断了，让调用方保证\n        userIdList.stream()\n                .map(this::getUserSession)\n                .filter(Objects::nonNull)\n                .forEach(consumer);\n    }\n\n    /**\n     * true 用户存在\n     *\n     * @param userId 用户id\n     * @return true 用户存在\n     */\n    boolean existUserSession(long userId);\n\n    /**\n     * 设置 channel 的 userId，表示已经身份验证了（即登录过了）。\n     *\n     * @param userChannelId userChannelId\n     * @param userId        userId\n     * @return true 设置成功\n     */\n    boolean settingUserId(UserChannelId userChannelId, long userId);\n\n    /**\n     * 移除 UserSession\n     *\n     * @param userSession userSession\n     */\n    void removeUserSession(Session userSession);\n\n    /**\n     * 根据 userId 移除 UserSession ，在移除前发送一个消息\n     * <pre>\n     *     玩家存在时会触发\n     * </pre>\n     *\n     * @param userId userId\n     * @param msg    msg\n     */\n    void removeUserSession(long userId, Object msg);\n\n    /**\n     * userHook\n     *\n     * @param userHook userHook\n     */\n    void setUserHook(UserHook userHook);\n\n    /**\n     * 当前在线人数\n     *\n     * @return 当前在线人数\n     */\n    int countOnline();\n\n    /**\n     * 全员消息广播\n     * 消息类型 ExternalMessage\n     *\n     * @param msg 消息\n     */\n    void broadcast(Object msg);\n\n    /**\n     * 遍历所有玩家\n     *\n     * @param consumer consumer\n     */\n    void forEach(Consumer<Session> consumer);\n}\n"
  },
  {
    "path": "external/external-core/src/main/java/com/iohao/game/external/core/session/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 游戏对外服 - UserSessions\n *\n * @author 渔民小镇\n * @date 2024-09-13\n */\npackage com.iohao.game.external.core.session;"
  },
  {
    "path": "external/external-netty/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.iohao.game</groupId>\n        <artifactId>ioGame</artifactId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n\n    <artifactId>external-netty</artifactId>\n    <name>external-netty for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>external-core</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalCore.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.core.codec.ProtoDataCodec;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.PresentKit;\nimport com.iohao.game.common.kit.ProtoKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.external.core.ExternalCore;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.hook.UserHook;\nimport com.iohao.game.external.core.hook.internal.DefaultUserHook;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport com.iohao.game.external.core.micro.MicroBootstrap;\nimport com.iohao.game.external.core.micro.join.ExternalJoinSelector;\nimport com.iohao.game.external.core.micro.join.ExternalJoinSelectors;\nimport com.iohao.game.external.core.session.UserSessionOption;\nimport com.iohao.game.external.core.session.UserSessions;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/**\n * netty ExternalCore\n *\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic final class DefaultExternalCore implements ExternalCore {\n    final DefaultExternalCoreSetting setting;\n\n    DefaultExternalCore(DefaultExternalCoreSetting setting) {\n        this.setting = setting;\n    }\n\n    @Override\n    public MicroBootstrap createMicroBootstrap() {\n        check();\n\n        defaultSetting();\n\n        final int externalCorePort = this.setting.getExternalCorePort();\n\n        if (IoGameGlobalConfig.openLog) {\n            var gameExternalServerTip = Bundle.getMessage(MessageKey.gameExternalServer);\n            var connectionWayTip = Bundle.getMessage(MessageKey.connectionWay);\n\n            log.info(\"{} port: [{}] - {}: [{}] \"\n                    , gameExternalServerTip\n                    , externalCorePort\n                    , connectionWayTip\n                    , setting.getExternalJoinEnum().getName());\n        }\n\n        aware();\n\n        // 此服务器是和真实用户连接的\n        MicroBootstrap microBootstrap = this.setting.getMicroBootstrap();\n        microBootstrap.setExternalCoreSetting(this.setting);\n\n        return microBootstrap;\n    }\n\n    private void check() {\n\n        int externalCorePort = setting.getExternalCorePort();\n        if (externalCorePort <= 0) {\n            ThrowKit.ofIllegalArgumentException(\"The externalServer port must be >0 \" + externalCorePort);\n        }\n\n        Objects.requireNonNull(setting.getExternalJoinEnum()\n                , \"Please set a ExternalJoinEnum:\" + Arrays.toString(ExternalJoinEnum.values()));\n    }\n\n    /**\n     * 一些默认值设置，由于连接类型的代码并不多，就在这里硬编码了。\n     */\n    private void defaultSetting() {\n        // 根据当前的连接方式得到 ExternalJoinSelector\n        ExternalJoinEnum joinEnum = this.setting.getExternalJoinEnum();\n        ExternalJoinSelector externalJoinSelector = ExternalJoinSelectors.getExternalJoinSelector(joinEnum);\n        // 初始化一些数据\n        externalJoinSelector.defaultSetting(this.setting);\n\n        // ================== 以下是在各连接方式下都通用的设置 ==================\n\n        // UserHook 钩子接口；如果开发者没有手动赋值，则给一个默认的\n        PresentKit.ifNull(this.setting.getUserHook(), () -> this.setting.setUserHook(new DefaultUserHook()));\n        UserSessions<?, ?> userSessions = this.setting.getUserSessions();\n        userSessions.setUserHook(this.setting.getUserHook());\n\n        // 当前游戏对外服所使用的连接方式\n        userSessions.option(UserSessionOption.externalJoin, joinEnum);\n\n        // ================== 其他 ==================\n        if (DataCodecKit.getDataCodec() instanceof ProtoDataCodec) {\n            ProtoKit.create(ExternalMessage.class);\n        }\n    }\n\n    private void aware() {\n        // 玩家上线、下线钩子接口\n        UserHook userHook = setting.getUserHook();\n        var userSessions = setting.getUserSessions();\n        userSessions.setUserHook(userHook);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalCoreSetting.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty;\n\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport com.iohao.game.common.kit.system.OsInfo;\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.external.core.aware.ExternalCoreSettingAware;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.core.common.cmd.DefaultCmdRegions;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.hook.UserHook;\nimport com.iohao.game.external.core.hook.internal.IdleProcessSetting;\nimport com.iohao.game.external.core.micro.MicroBootstrap;\nimport com.iohao.game.external.core.micro.MicroBootstrapFlow;\nimport com.iohao.game.external.core.netty.micro.auto.GroupChannelOption;\nimport com.iohao.game.external.core.netty.micro.auto.GroupChannelOptionForLinux;\nimport com.iohao.game.external.core.netty.micro.auto.GroupChannelOptionForMac;\nimport com.iohao.game.external.core.netty.micro.auto.GroupChannelOptionForOther;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.epoll.Epoll;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * ExternalCoreSetting\n *\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultExternalCoreSetting implements ExternalCoreSetting {\n    /** 动态属性 */\n    final AttrOptions options = new AttrOptions();\n    final CmdRegions cmdRegions = new DefaultCmdRegions();\n    /** 目前 ioGame 没有自己的容器管理 IOC/AOP，先用这个变量顶着 */\n    final Set<Object> injectObject = new NonBlockingHashSet<>();\n    /** 真实玩家连接的端口 */\n    @Setter\n    int externalCorePort;\n    /** 连接方式：默认为 Websocket */\n    @Setter\n    ExternalJoinEnum externalJoinEnum = ExternalJoinEnum.WEBSOCKET;\n    /** 游戏对外服-与真实玩家连接的服务器 */\n    MicroBootstrap microBootstrap;\n    /** 与真实玩家连接服务器的启动流程 */\n    MicroBootstrapFlow<?> microBootstrapFlow;\n    /** 心跳相关的设置 */\n    IdleProcessSetting idleProcessSetting;\n    /** 用户（玩家）session 管理器 */\n    UserSessions<?, ?> userSessions;\n    /** UserHook 钩子接口，上线时、下线时会触发 */\n    UserHook userHook;\n    /** 与 Broker（游戏网关）通信的 client */\n    @Setter\n    BrokerClient brokerClient;\n    /** external netty GroupChannelOption */\n    @Setter\n    GroupChannelOption groupChannelOption;\n\n    public void inject() {\n        this.injectObject.forEach(this::aware);\n\n        // 心跳 hook 特殊处理\n        if (Objects.nonNull(this.idleProcessSetting)) {\n            this.aware(this.idleProcessSetting.getIdleHook());\n        }\n    }\n\n    @Override\n    public void aware(Object o) {\n        if (o instanceof UserSessionsAware aware) {\n            aware.setUserSessions(this.userSessions);\n        }\n\n        if (o instanceof BrokerClientAware aware) {\n            aware.setBrokerClient(this.brokerClient);\n        }\n\n        if (o instanceof CmdRegionsAware aware) {\n            aware.setCmdRegions(this.cmdRegions);\n        }\n\n        if (o instanceof ExternalCoreSettingAware aware) {\n            aware.setExternalCoreSetting(this);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> MicroBootstrapFlow<T> getMicroBootstrapFlow() {\n        return (MicroBootstrapFlow<T>) microBootstrapFlow;\n    }\n\n    public void setMicroBootstrap(MicroBootstrap microBootstrap) {\n        this.microBootstrap = microBootstrap;\n        this.injectObject.add(this.microBootstrap);\n    }\n\n    public void setMicroBootstrapFlow(MicroBootstrapFlow<?> microBootstrapFlow) {\n        this.microBootstrapFlow = microBootstrapFlow;\n        this.injectObject.add(this.microBootstrapFlow);\n    }\n\n    public void setIdleProcessSetting(IdleProcessSetting idleProcessSetting) {\n        this.idleProcessSetting = idleProcessSetting;\n        this.injectObject.add(this.idleProcessSetting);\n    }\n\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n        this.injectObject.add(this.userSessions);\n    }\n\n    public void setUserHook(UserHook userHook) {\n        this.userHook = userHook;\n        this.injectObject.add(this.userHook);\n    }\n\n    public GroupChannelOption getGroupChannelOption() {\n        if (Objects.isNull(this.groupChannelOption)) {\n            this.groupChannelOption = createGroupChannelOption();\n        }\n\n        return this.groupChannelOption;\n    }\n\n    private GroupChannelOption createGroupChannelOption() {\n        if (OsInfo.isMac()) {\n            return new GroupChannelOptionForMac();\n        }\n\n        // #375，Lightweight or embedded Linux distributions may not have fulled I/O multiplexing support\n        if (OsInfo.isLinux() && Epoll.isAvailable()) {\n            return new GroupChannelOptionForLinux();\n        }\n\n        // other system nio\n        return new GroupChannelOptionForOther();\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalServer.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty;\n\nimport com.iohao.game.bolt.broker.client.BrokerClientApplication;\nimport com.iohao.game.bolt.broker.core.GroupWith;\nimport com.iohao.game.bolt.broker.core.client.BrokerAddress;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.external.core.ExternalCore;\nimport com.iohao.game.external.core.ExternalServer;\nimport com.iohao.game.external.core.broker.client.ExternalBrokerClientStartup;\nimport com.iohao.game.external.core.micro.MicroBootstrap;\nimport com.iohao.game.external.core.micro.join.ExternalJoinSelector;\nimport com.iohao.game.external.core.micro.join.ExternalJoinSelectors;\n\nimport java.util.Objects;\nimport java.util.ServiceLoader;\n\n/**\n * 游戏对外服由两部分构成\n * <pre>\n *     1. 与真实玩家连接的 ExternalCore 服务器\n *     2. 与 Broker（游戏网关）通信的 BrokerClient ExternalBrokerClientStartup 服务器\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-19\n */\npublic final class DefaultExternalServer implements ExternalServer, GroupWith {\n    /** 与真实玩家连接的 ExternalCore 服务器 */\n    ExternalCore externalCore;\n    /** ExternalCore 的一些设置 */\n    DefaultExternalCoreSetting setting;\n\n    /** 与 Broker（游戏网关）通信的 BrokerClient */\n    ExternalBrokerClientStartup externalBrokerClientStartup;\n    /** 连接 broker （游戏网关） 的地址 */\n    BrokerAddress brokerAddress;\n    int withNo;\n\n    static {\n        ServiceLoader.load(ExternalJoinSelector.class).forEach(ExternalJoinSelectors::putIfAbsent);\n    }\n\n    DefaultExternalServer(DefaultExternalCoreSetting setting\n            , ExternalCore externalCore\n            , BrokerAddress brokerAddress\n            , ExternalBrokerClientStartup externalBrokerClientStartup\n    ) {\n        this.setting = setting;\n        this.brokerAddress = brokerAddress;\n        this.externalCore = externalCore;\n        this.externalBrokerClientStartup = externalBrokerClientStartup;\n    }\n\n    @Override\n    public void startup() {\n        // 创建与真实玩家通信的 netty 服务器\n        MicroBootstrap microBootstrap = this.externalCore.createMicroBootstrap();\n\n        var startExternalBrokerClient = System.getProperty(\"ExternalBrokerClientStartup\", \"true\");\n        if (Boolean.parseBoolean(startExternalBrokerClient)) {\n            // 启动与 Broker 游戏网关通信的 BrokerClient\n            startExternalBrokerClient();\n        }\n\n        this.setting.inject();\n\n        // 启动与真实玩家通信的 netty 服务器\n        microBootstrap.startup();\n    }\n\n    private void startExternalBrokerClient() {\n        this.externalBrokerClientStartup.setWithNo(this.withNo);\n        // 与 Broker 游戏网关通信的 BrokerClient\n        var brokerClientBuilder = BrokerClientApplication.initConfig(this.externalBrokerClientStartup);\n        // aware 注入扩展\n        brokerClientBuilder.awareInject(setting);\n\n        // 与 broker 游戏网关通信的服务器地址\n        if (Objects.nonNull(this.brokerAddress)) {\n            brokerClientBuilder.brokerAddress(this.brokerAddress);\n        }\n\n        // 启动与 Broker 游戏网关通信的 BrokerClient\n        BrokerClient brokerClient = BrokerClientApplication.start(brokerClientBuilder);\n\n        this.externalBrokerClientStartup.startupSuccess(brokerClient);\n\n        this.setting.setBrokerClient(brokerClient);\n    }\n\n    public static DefaultExternalServerBuilder newBuilder(int externalCorePort) {\n        return new DefaultExternalServerBuilder(externalCorePort);\n    }\n\n    @Override\n    public void setWithNo(int withNo) {\n        this.withNo = withNo;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalServerBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerAddress;\nimport com.iohao.game.external.core.ExternalCore;\nimport com.iohao.game.external.core.ExternalServer;\nimport com.iohao.game.external.core.broker.client.ExternalBrokerClientStartup;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@Getter\n@Setter\n@Accessors(fluent = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultExternalServerBuilder {\n\n    final DefaultExternalCoreSetting setting = new DefaultExternalCoreSetting();\n\n    /** 内部逻辑服 连接网关服务器，与网关通信 */\n    ExternalBrokerClientStartup externalBrokerClientStartup = new ExternalBrokerClientStartup();\n\n    /** 设置 broker （游戏网关）连接地址 */\n    BrokerAddress brokerAddress;\n\n    DefaultExternalServerBuilder(int externalCorePort) {\n        this.setting.setExternalCorePort(externalCorePort);\n    }\n\n    /**\n     * 连接方式；如果不设置，默认是 websocket\n     *\n     * @param joinEnum 连接方式\n     * @return this\n     */\n    public DefaultExternalServerBuilder externalJoinEnum(ExternalJoinEnum joinEnum) {\n        this.setting.setExternalJoinEnum(joinEnum);\n        return this;\n    }\n\n    public ExternalServer build() {\n        this.check();\n\n        // 与真实玩家通信的 netty 服务器\n        ExternalCore externalCore = new DefaultExternalCore(this.setting);\n\n        // 游戏对外服\n        return new DefaultExternalServer(\n                this.setting,\n                externalCore,\n                this.brokerAddress,\n                this.externalBrokerClientStartup\n        );\n    }\n\n    private void check() {\n        Objects.requireNonNull(this.externalBrokerClientStartup);\n    }\n\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/SettingOption.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty;\n\nimport com.iohao.game.common.kit.attr.AttrOption;\nimport com.iohao.game.external.core.netty.handler.SocketCmdAccessAuthHandler;\nimport com.iohao.game.external.core.netty.handler.SocketIdleHandler;\nimport com.iohao.game.external.core.netty.handler.SocketRequestBrokerHandler;\nimport com.iohao.game.external.core.netty.handler.SocketUserSessionHandler;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-29\n */\npublic interface SettingOption {\n    AttrOption<SocketUserSessionHandler> socketUserSessionHandler =\n            AttrOption.valueOf(\"SocketUserSessionHandler\");\n\n    AttrOption<SocketCmdAccessAuthHandler> socketCmdAccessAuthHandler =\n            AttrOption.valueOf(\"SocketCmdAccessAuthHandler\");\n\n    AttrOption<SocketRequestBrokerHandler> socketRequestBrokerHandler =\n            AttrOption.valueOf(\"SocketRequestBrokerHandler\");\n\n    AttrOption<SocketIdleHandler> socketIdleHandler = AttrOption.valueOf(\"SocketIdleHandler\");\n\n\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/CmdCacheHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.hook.cache.ExternalCmdCache;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\n\nimport java.util.Objects;\n\n/**\n * 游戏对外服缓存\n *\n * @author 渔民小镇\n * @date 2023-07-02\n */\n@ChannelHandler.Sharable\npublic final class CmdCacheHandler extends ChannelInboundHandlerAdapter {\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        ExternalCmdCache externalCmdCache = ExternalGlobalConfig.externalCmdCache;\n        if (Objects.isNull(externalCmdCache)) {\n            // 如果没有配置缓存，移除自身处理器\n            ctx.pipeline().remove(this);\n        }\n\n        super.channelActive(ctx);\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        if (msg instanceof BarMessage message) {\n            ExternalCmdCache externalCmdCache = ExternalGlobalConfig.externalCmdCache;\n\n            if (externalCmdCache != null) {\n                BarMessage cache = externalCmdCache.getCache(message);\n                if (Objects.nonNull(cache)) {\n                    // 从缓存中取到了数据，直接返回缓存数据\n                    ctx.writeAndFlush(cache);\n                    return;\n                }\n            }\n\n            ctx.fireChannelRead(message);\n        } else {\n            ctx.fireChannelRead(msg);\n        }\n    }\n\n    public CmdCacheHandler() {\n    }\n\n    public static CmdCacheHandler me() {\n        return Holder.ME;\n    }\n\n    private static class Holder {\n        static final CmdCacheHandler ME = new CmdCacheHandler();\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/CmdCheckHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\n\n/**\n * 路由是否存在检测\n * <pre>\n *     <a href=\"https://github.com/iohao/ioGame/issues/115\">115</a>\n *\n *     当路由不存在时，可以起到抵挡的作用，而不必经过其他服务器。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-05-01\n */\n@ChannelHandler.Sharable\npublic final class CmdCheckHandler extends ChannelInboundHandlerAdapter\n        implements CmdRegionsAware {\n    CmdRegions cmdRegions;\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        if (msg instanceof BarMessage message) {\n            int cmdMerge = message.getHeadMetadata().getCmdMerge();\n\n            // 2. 检查路由是否存在\n            if (cmdRegions.existCmdMerge(cmdMerge)) {\n                ctx.fireChannelRead(message);\n            } else {\n                ExternalCodecKit.employError(message, ActionErrorEnum.cmdInfoErrorCode);\n                ctx.writeAndFlush(message);\n            }\n        } else {\n            ctx.fireChannelRead(msg);\n        }\n    }\n\n    @Override\n    public void setCmdRegions(CmdRegions cmdRegions) {\n        this.cmdRegions = cmdRegions;\n    }\n\n    public CmdCheckHandler() {\n    }\n\n    public static CmdCheckHandler me() {\n        return Holder.ME;\n    }\n\n    private static class Holder {\n        static final CmdCheckHandler ME = new CmdCheckHandler();\n    }\n}"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SimpleLoggerHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 简单日志打印，通常是不活跃的连接、触发异常的连接\n *\n * @author 渔民小镇\n * @date 2024-05-01\n * @since 21.7\n */\n@Slf4j\n@ChannelHandler.Sharable\npublic final class SimpleLoggerHandler extends ChannelInboundHandlerAdapter {\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        super.channelInactive(ctx);\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        log.error(cause.getMessage(), cause);\n        super.exceptionCaught(ctx, cause);\n    }\n\n    private SimpleLoggerHandler() {\n    }\n\n    public static SimpleLoggerHandler me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final SimpleLoggerHandler ME = new SimpleLoggerHandler();\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketCmdAccessAuthHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.hook.AccessAuthenticationHook;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.netty.session.SocketUserSessions;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\n\n/**\n * 路由访问权限相关处理\n *\n * @author 渔民小镇\n * @date 2023-05-05\n */\n@ChannelHandler.Sharable\npublic class SocketCmdAccessAuthHandler extends ChannelInboundHandlerAdapter\n        implements UserSessionsAware {\n\n    protected UserSessions<?, ?> userSessions;\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        if (msg instanceof BarMessage message) {\n            if (reject(ctx, message)) {\n                // 拒绝玩家直接访问 action\n                return;\n            }\n\n            SocketUserSessions socketUserSessions = (SocketUserSessions) this.userSessions;\n            var userSession = socketUserSessions.getUserSession(ctx);\n            if (userSession != null) {\n                boolean loginSuccess = userSession.isVerifyIdentity();\n                if (notPass(ctx, message, loginSuccess)) {\n                    // 访问了需要登录才能访问的 action\n                    return;\n                }\n            }\n\n            ctx.fireChannelRead(message);\n        } else {\n            ctx.fireChannelRead(msg);\n        }\n    }\n\n    protected boolean reject(ChannelHandlerContext ctx, BarMessage message) {\n        AccessAuthenticationHook accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;\n        int cmdMerge = message.getHeadMetadata().getCmdMerge();\n        boolean reject = accessAuthenticationHook.reject(cmdMerge);\n\n        if (reject) {\n            ExternalCodecKit.employError(message, ActionErrorEnum.cmdInfoErrorCode);\n            ctx.writeAndFlush(message);\n            return true;\n        }\n\n        return false;\n    }\n\n    protected boolean notPass(ChannelHandlerContext ctx, BarMessage message, boolean loginSuccess) {\n        // 是否可以访问业务方法（action），true 表示可以访问该路由对应的业务方法\n        AccessAuthenticationHook accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;\n        int cmdMerge = message.getHeadMetadata().getCmdMerge();\n        boolean pass = accessAuthenticationHook.pass(loginSuccess, cmdMerge);\n\n        if (pass) {\n            return false;\n        }\n\n        // 当访问验证没通过，通知玩家\n        ExternalCodecKit.employError(message, ActionErrorEnum.verifyIdentity);\n\n        // 响应结果给玩家\n        ctx.writeAndFlush(message);\n\n        return true;\n    }\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = userSessions;\n    }\n}"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketIdleExcludeHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.message.ExternalMessageCmdCode;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\n\n/**\n * Exclude heartbeat message\n *\n * @author 渔民小镇\n * @date 2024-11-09\n * @since 21.20\n */\n@ChannelHandler.Sharable\npublic final class SocketIdleExcludeHandler extends ChannelInboundHandlerAdapter {\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        BarMessage message = (BarMessage) msg;\n\n        int cmdCode = message.getHeadMetadata().getCmdCode();\n        if (cmdCode == ExternalMessageCmdCode.idle) {\n            return;\n        }\n\n        // 交给下一个业务处理 (handler) , 下一个业务指的是你编排 handler 时的顺序\n        ctx.fireChannelRead(msg);\n    }\n\n    private SocketIdleExcludeHandler() {\n    }\n\n    public static SocketIdleExcludeHandler me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final SocketIdleExcludeHandler ME = new SocketIdleExcludeHandler();\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketIdleHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.external.core.aware.ExternalCoreSettingAware;\nimport com.iohao.game.external.core.hook.IdleHook;\nimport com.iohao.game.external.core.hook.internal.IdleProcessSetting;\nimport com.iohao.game.external.core.message.ExternalMessageCmdCode;\nimport com.iohao.game.external.core.netty.DefaultExternalCoreSetting;\nimport com.iohao.game.external.core.netty.session.SocketUserSession;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.timeout.IdleStateEvent;\n\nimport java.util.Objects;\n\n/**\n * 心跳相关的 Handler\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\n@ChannelHandler.Sharable\npublic final class SocketIdleHandler extends ChannelInboundHandlerAdapter\n        implements ExternalCoreSettingAware {\n    /** 心跳事件回调 */\n    IdleHook<IdleStateEvent> idleHook;\n    /** true : 响应心跳给客户端 */\n    boolean pong;\n    UserSessions<ChannelHandlerContext, SocketUserSession> userSessions;\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n\n        BarMessage message = (BarMessage) msg;\n\n        int cmdCode = message.getHeadMetadata().getCmdCode();\n        if (cmdCode != ExternalMessageCmdCode.idle) {\n            // 不是心跳请求. 交给下一个业务处理 (handler) , 下一个业务指的是你编排 handler 时的顺序\n            ctx.fireChannelRead(msg);\n            return;\n        }\n\n        // 心跳处理\n        if (this.pong) {\n\n            if (Objects.nonNull(this.idleHook)) {\n                // 心跳响应前的处理\n                this.idleHook.pongBefore(message);\n            }\n\n            ctx.writeAndFlush(message);\n        }\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof IdleStateEvent event) {\n\n            boolean close = true;\n\n            var userSession = userSessions.getUserSession(ctx);\n\n            // 执行用户定义的心跳回调事件处理\n            if (Objects.nonNull(idleHook)) {\n                close = idleHook.callback(userSession, event);\n            }\n\n            // close ctx\n            if (close) {\n                this.userSessions.removeUserSession(userSession);\n            }\n\n        } else {\n            super.userEventTriggered(ctx, evt);\n        }\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void setExternalCoreSetting(ExternalCoreSetting externalCoreSetting) {\n\n        if (Objects.nonNull(this.userSessions)) {\n            return;\n        }\n\n        DefaultExternalCoreSetting setting = (DefaultExternalCoreSetting) externalCoreSetting;\n        IdleProcessSetting idleProcessSetting = setting.getIdleProcessSetting();\n        this.idleHook = idleProcessSetting.getIdleHook();\n        this.pong = idleProcessSetting.isPong();\n        this.userSessions = (UserSessions<ChannelHandlerContext, SocketUserSession>) setting.getUserSessions();\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketRequestBrokerHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.trace.TraceKit;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.netty.session.SocketUserSession;\nimport com.iohao.game.external.core.netty.session.SocketUserSessions;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@Setter\n@ChannelHandler.Sharable\n@Slf4j(topic = IoGameLogName.ExternalTopic)\npublic final class SocketRequestBrokerHandler extends SimpleChannelInboundHandler<BarMessage>\n        implements UserSessionsAware, BrokerClientAware {\n\n    BrokerClient brokerClient;\n    SocketUserSessions userSessions;\n\n    @Override\n    protected void channelRead0(ChannelHandlerContext ctx, BarMessage message) {\n        // 给请求消息加上一些 user 自身的数据\n        SocketUserSession userSession = this.userSessions.getUserSession(ctx);\n        userSession.employ(message);\n\n        if (IoGameGlobalConfig.openTraceId) {\n            HeadMetadata headMetadata = message.getHeadMetadata();\n            headMetadata.setTraceId(TraceKit.newTraceId());\n        }\n\n        try {\n            // 请求游戏网关，Broker（游戏网关）会将请求转发到具体的游戏逻辑服\n            brokerClient.oneway(message);\n        } catch (Exception e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = (SocketUserSessions) userSessions;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketUserSessionHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler;\n\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.netty.session.SocketUserSession;\nimport com.iohao.game.external.core.netty.session.SocketUserSessions;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@Setter\n@ChannelHandler.Sharable\n@Slf4j(topic = IoGameLogName.ExternalTopic)\npublic final class SocketUserSessionHandler extends ChannelInboundHandlerAdapter\n        implements UserSessionsAware, BrokerClientAware {\n\n    SocketUserSessions userSessions;\n    BrokerClient brokerClient;\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = (SocketUserSessions) userSessions;\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        BrokerClientModuleMessage moduleMessage = brokerClient.getBrokerClientModuleMessage();\n        int idHash = moduleMessage.getIdHash();\n\n        // 加入到 session 管理\n        SocketUserSession userSession = userSessions.add(ctx);\n        userSession.setExternalClientId(idHash);\n\n        super.channelActive(ctx);\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        // 从 session 管理中移除\n        var userSession = this.userSessions.getUserSession(ctx);\n        this.userSessions.removeUserSession(userSession);\n\n        super.channelInactive(ctx);\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        // 从 session 管理中移除\n        var userSession = this.userSessions.getUserSession(ctx);\n        this.userSessions.removeUserSession(userSession);\n\n        super.exceptionCaught(ctx, cause);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/check/HttpFallbackHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler.check;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.util.ReferenceCountUtil;\n\n/**\n * @author 渔民小镇\n * @date 2025-06-28\n * @since 21.29\n */\n@ChannelHandler.Sharable\npublic final class HttpFallbackHandler extends ChannelInboundHandlerAdapter {\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        if (!(msg instanceof FullHttpRequest req)) {\n            ctx.fireChannelRead(msg);\n            return;\n        }\n\n        if (\"websocket\".equalsIgnoreCase(req.headers().get(HttpHeaderNames.UPGRADE))) {\n            ctx.fireChannelRead(msg);\n            return;\n        }\n\n        ReferenceCountUtil.release(msg);\n        ctx.close();\n    }\n\n    private HttpFallbackHandler() {\n    }\n\n    public static HttpFallbackHandler me() {\n        return Holder.ME;\n    }\n\n    private static class Holder {\n        static final HttpFallbackHandler ME = new HttpFallbackHandler();\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/check/TcpProtocolSanityCheckHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler.check;\n\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.ByteToMessageDecoder;\n\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2025-06-28\n * @since 21.29\n */\npublic final class TcpProtocolSanityCheckHandler extends ByteToMessageDecoder {\n    private boolean closed = false;\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {\n\n        if (closed || in.readableBytes() < 4) {\n            return;\n        }\n\n        in.markReaderIndex();\n        int length = in.readInt();\n        in.resetReaderIndex();\n\n        if (length <= 0 || length > ExternalGlobalConfig.CoreOption.packageMaxSize) {\n            closed = true;\n            ctx.close();\n        } else {\n            ctx.pipeline().remove(this);\n        }\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/codec/TcpExternalCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler.codec;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageCodec;\n\nimport java.util.List;\n\n/**\n * Tcp 编解码器\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic final class TcpExternalCodec extends MessageToMessageCodec<ByteBuf, BarMessage> {\n    @Override\n    protected void encode(ChannelHandlerContext ctx, BarMessage message, List<Object> out) {\n        /*\n         * 编码器 - 【游戏对外服】发送消息给【游戏客户端、请求端】\n         * ResponseMessage ---> ExternalMessage ---> 字节数组\n         */\n        ExternalMessage externalMessage = ExternalCodecKit.convertExternalMessage(message);\n\n        byte[] bytes = DataCodecKit.encode(externalMessage);\n\n        /*\n         * 使用默认 buffer 。如果没有做任何配置，通常默认实现为池化的 direct （直接内存，也称为堆外内存）\n         * 优点：使用的系统内存，读写效率高（少一次拷贝），且不受 GC 影响\n         * 缺点：分配效率低\n         */\n        ByteBuf buffer = ctx.alloc().buffer(bytes.length + 4);\n        // 消息长度\n        buffer.writeInt(bytes.length);\n        // 消息\n        buffer.writeBytes(bytes);\n\n        out.add(buffer);\n    }\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {\n        /*\n         * 解码器 - 接收【游戏客户端、请求端】的消息\n         * 字节数组 ---> ExternalMessage ---> RequestMessage\n         */\n        // 读取消息长度\n        int length = msg.readInt();\n        // 消息\n        byte[] msgBytes = new byte[length];\n        msg.readBytes(msgBytes);\n\n        ExternalMessage externalMessage = DataCodecKit.decode(msgBytes, ExternalMessage.class);\n\n        BarMessage message = ExternalCodecKit.convertRequestMessage(externalMessage);\n\n        //【游戏对外服】接收【游戏客户端】的消息\n        out.add(message);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/codec/WebSocketExternalCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler.codec;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageCodec;\nimport io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;\n\nimport java.util.List;\n\n/**\n * WebSocket 编解码器\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic class WebSocketExternalCodec extends MessageToMessageCodec<BinaryWebSocketFrame, BarMessage> {\n    @Override\n    protected void encode(ChannelHandlerContext ctx, BarMessage message, List<Object> out) {\n        /*\n         * 编码器 - 将消息发送到请求端（客户端）；【游戏对外服】发送消息给【游戏客户端】\n         * ResponseMessage ---> ExternalMessage ---> 字节数组\n         */\n        ExternalMessage externalMessage = ExternalCodecKit.convertExternalMessage(message);\n\n        byte[] bytes = DataCodecKit.encode(externalMessage);\n\n        // 使用默认 buffer 。如果没有做任何配置，通常默认实现为池化的 direct （直接内存，也称为堆外内存）\n        ByteBuf byteBuf = ctx.alloc().buffer(bytes.length);\n        byteBuf.writeBytes(bytes);\n\n        BinaryWebSocketFrame socketFrame = new BinaryWebSocketFrame(byteBuf);\n        out.add(socketFrame);\n    }\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, BinaryWebSocketFrame binary, List<Object> out) {\n        /*\n         * 解码器 - 接收请求端的消息（客户端）；\n         * 字节数组 ---> ExternalMessage ---> RequestMessage\n         */\n        ByteBuf contentBuf = binary.content();\n        byte[] bytes = new byte[contentBuf.readableBytes()];\n        contentBuf.readBytes(bytes);\n\n        ExternalMessage externalMessage = DataCodecKit.decode(bytes, ExternalMessage.class);\n\n        BarMessage message = ExternalCodecKit.convertRequestMessage(externalMessage);\n\n        //【游戏对外服】接收【游戏客户端】的消息\n        out.add(message);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/ws/HttpRealIpHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler.ws;\n\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.netty.session.SocketUserSessions;\nimport com.iohao.game.external.core.session.UserSessionOption;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Optional;\n\n/**\n * 获取玩家真实 ip\n * <pre>\n * nginx 配置\n *\n * server {\n *     location /websocket {\n *         proxy_http_version 1.1;\n *         proxy_set_header Upgrade $http_upgrade;\n *         proxy_set_header Connection \"Upgrade\";\n *         proxy_set_header X-Real-IP $remote_addr;\n *         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n *         proxy_set_header Host $host;\n *     }\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-08-16\n */\n@Slf4j\npublic final class HttpRealIpHandler extends ChannelInboundHandlerAdapter\n        implements UserSessionsAware {\n\n    SocketUserSessions userSessions;\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = (SocketUserSessions) userSessions;\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof FullHttpRequest request) {\n            HttpHeaders headers = request.headers();\n            String realIp = headers.get(\"X-Real-IP\");\n            Optional.ofNullable(userSessions.getUserSession(ctx))\n                    .ifPresent(session -> session.option(UserSessionOption.realIp, realIp));\n\n            ctx.pipeline().remove(this);\n        }\n\n        super.channelRead(ctx, msg);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/ws/WebSocketVerifyHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.handler.ws;\n\nimport com.iohao.game.external.core.aware.UserSessionsAware;\nimport com.iohao.game.external.core.netty.session.SocketUserSession;\nimport com.iohao.game.external.core.netty.session.SocketUserSessions;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.*;\n\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * WebSocket 连接前的 token 验证 handler\n *\n * @author 渔民小镇\n * @date 2023-08-03\n */\npublic class WebSocketVerifyHandler extends ChannelInboundHandlerAdapter\n        implements UserSessionsAware {\n\n    protected SocketUserSessions userSessions;\n\n    @Override\n    public void setUserSessions(UserSessions<?, ?> userSessions) {\n        this.userSessions = (SocketUserSessions) userSessions;\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof FullHttpRequest request) {\n            // 从 uri 中解析参数\n            String uri = request.uri();\n            Map<String, String> params = getParams(uri);\n\n            // 开发者可以重写 verify 方法来扩展\n            SocketUserSession userSession = userSessions.getUserSession(ctx);\n            boolean verify = verify(userSession, params);\n\n            if (verify) {\n                //  验证通过后，移除自身；减少消息在 handler 中流动的次数\n                ctx.pipeline().remove(this);\n            } else {\n                // 验证失败，关闭连接或返回错误响应\n                FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED);\n                ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);\n                return;\n            }\n        }\n\n        super.channelRead(ctx, msg);\n    }\n\n    /**\n     * verify\n     *\n     * @param userSession ctx\n     * @param params      params\n     * @return 返回 false 表示验证没通过，框架会关闭连接\n     */\n    protected boolean verify(SocketUserSession userSession, Map<String, String> params) {\n        /*\n         * 保存一份验证完成的数据，后续使用。\n         * 开发者如果有想把解析后的数据传递到游戏逻辑服中的，\n         * 可以先在这里保存一份想要传递的数据。\n         */\n\n        return true;\n    }\n\n    protected Map<String, String> getParams(String uri) {\n        return new QueryStringDecoder(uri)\n                .parameters()\n                .entrySet()\n                .stream()\n                .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getFirst()));\n    }\n}"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/hook/DefaultSocketIdleHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.hook;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.session.UserSession;\nimport io.netty.handler.timeout.IdleState;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * tcp、websocket 心跳钩子\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic final class DefaultSocketIdleHook implements SocketIdleHook {\n\n    @Override\n    public boolean callback(UserSession userSession, IdleStateEvent event) {\n        IdleState state = event.state();\n        if (state == IdleState.READER_IDLE) {\n            /* 读超时 */\n            log.debug(\"READER_IDLE 读超时\");\n        } else if (state == IdleState.WRITER_IDLE) {\n            /* 写超时 */\n            log.debug(\"WRITER_IDLE 写超时\");\n        } else if (state == IdleState.ALL_IDLE) {\n            /* 总超时 */\n            log.debug(\"ALL_IDLE 总超时\");\n        }\n\n        BarMessage message = ExternalCodecKit.createErrorIdleMessage(ActionErrorEnum.idleErrorCode);\n        // 错误消息\n        message.setValidatorMsg(ActionErrorEnum.idleErrorCode.getMsg() + \" : \" + state.name());\n\n        // 通知客户端，触发了心跳事件\n        userSession.writeAndFlush(message);\n\n        // 返回 true 表示通知框架将当前的用户（玩家）连接关闭\n        return true;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/hook/SocketIdleHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.hook;\n\nimport com.iohao.game.external.core.hook.IdleHook;\nimport io.netty.handler.timeout.IdleStateEvent;\n\n/**\n * 长连接的心跳事件回调（TCP、WebSocket）\n *\n * @author 渔民小镇\n * @date 2023-06-02\n */\npublic interface SocketIdleHook extends IdleHook<IdleStateEvent> {\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/kit/ExternalServerCreateKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.kit;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerAddress;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.external.core.ExternalServer;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.netty.DefaultExternalServer;\nimport com.iohao.game.external.core.netty.DefaultExternalServerBuilder;\nimport lombok.experimental.UtilityClass;\n\n/**\n * 游戏对外服创建工具类，简化游戏对外服的构建\n *\n * @author 渔民小镇\n * @date 2023-05-30\n */\n@UtilityClass\npublic class ExternalServerCreateKit {\n    /**\n     * 创建游戏对外服\n     *\n     * @param externalCorePort 游戏对外服端口\n     * @param externalJoinEnum 连接方式\n     * @return 游戏对外服\n     */\n    public ExternalServer createExternalServer(int externalCorePort, ExternalJoinEnum externalJoinEnum) {\n        return newBuilder(externalCorePort, externalJoinEnum).build();\n    }\n\n    /**\n     * 创建游戏对外服构建器\n     *\n     * @param externalCorePort 游戏对外服端口\n     * @param externalJoinEnum 连接方式\n     * @return 游戏对外服构建器\n     */\n    public DefaultExternalServerBuilder newBuilder(\n            int externalCorePort\n            , ExternalJoinEnum externalJoinEnum) {\n\n        return DefaultExternalServer\n                // 游戏对外服端口；与真实玩家建立连接的端口\n                .newBuilder(externalCorePort)\n                // 连接方式\n                .externalJoinEnum(externalJoinEnum)\n                // 与 Broker （游戏网关）的连接地址 ；默认不填写也是这个值\n                .brokerAddress(new BrokerAddress(\"127.0.0.1\", IoGameGlobalConfig.brokerPort));\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/AbstractMicroBootstrap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro;\n\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.external.core.micro.MicroBootstrap;\nimport com.iohao.game.external.core.netty.DefaultExternalCoreSetting;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 与真实玩家连接的服务器\n *\n * @author 渔民小镇\n * @date 2023-05-27\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\nabstract class AbstractMicroBootstrap implements MicroBootstrap {\n\n    protected DefaultExternalCoreSetting setting;\n\n    @Override\n    public void setExternalCoreSetting(ExternalCoreSetting setting) {\n        this.setting = (DefaultExternalCoreSetting) setting;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/AbstractMicroBootstrapFlow.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro;\n\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.external.core.aware.ExternalCoreSettingAware;\nimport com.iohao.game.external.core.micro.MicroBootstrapFlow;\nimport com.iohao.game.external.core.netty.DefaultExternalCoreSetting;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-01\n */\n@FieldDefaults(level = AccessLevel.PROTECTED)\nabstract class AbstractMicroBootstrapFlow<T> implements MicroBootstrapFlow<T>, ExternalCoreSettingAware {\n    DefaultExternalCoreSetting setting;\n\n    @Override\n    public void setExternalCoreSetting(ExternalCoreSetting externalCoreSetting) {\n        this.setting = (DefaultExternalCoreSetting) externalCoreSetting;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/DefaultPipelineContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro;\n\nimport com.iohao.game.external.core.netty.DefaultExternalCoreSetting;\nimport com.iohao.game.external.core.micro.PipelineContext;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelPipeline;\n\n/**\n * PipelineContext 主要用于包装 SocketChannel channel\n * <pre>\n *     目的是为了增强 Handler aware 能力\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-04-26\n */\npublic record DefaultPipelineContext(Channel channel, DefaultExternalCoreSetting setting)\n        implements PipelineContext {\n\n    @Override\n    public void addFirst(String name, Object handler) {\n\n        this.setting.aware(handler);\n\n        if (handler instanceof ChannelHandler channelHandler) {\n            ChannelPipeline pipeline = channel.pipeline();\n            pipeline.addFirst(name, channelHandler);\n        }\n    }\n\n    @Override\n    public void addLast(String name, Object handler) {\n\n        // aware 能力附加\n        this.setting.aware(handler);\n\n        if (handler instanceof ChannelHandler channelHandler) {\n            ChannelPipeline pipeline = channel.pipeline();\n            pipeline.addLast(name, channelHandler);\n        }\n    }\n\n    @Override\n    public void remove(String name) {\n        ChannelPipeline pipeline = channel.pipeline();\n        pipeline.remove(name);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/SocketMicroBootstrap.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro;\n\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.core.micro.MicroBootstrapFlow;\nimport com.iohao.game.external.core.netty.micro.auto.GroupChannelOption;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.ServerChannel;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 与真实玩家连接的服务器，处理 tcp、websocket 的 netty 服务器。\n * <p>\n * 开发者可以继承后重写部分方法，来满足业务需求\n *\n * @author 渔民小镇\n * @date 2023-05-27\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Slf4j(topic = IoGameLogName.ExternalTopic)\npublic final class SocketMicroBootstrap extends AbstractMicroBootstrap {\n    @Override\n    public void startup() {\n        // 线程组相关\n        GroupChannelOption groupChannelOption = this.setting.getGroupChannelOption();\n        EventLoopGroup bossGroup = groupChannelOption.bossGroup();\n        EventLoopGroup workerGroup = groupChannelOption.workerGroup();\n        Class<? extends ServerChannel> channelClass = groupChannelOption.channelClass();\n\n        // netty 服务器\n        ServerBootstrap bootstrap = new ServerBootstrap()\n                .group(bossGroup, workerGroup)\n                .channel(channelClass);\n\n        // 开发者可以选择性的重写流程方法，来定制符合自身项目的业务\n        MicroBootstrapFlow<ServerBootstrap> microBootstrapFlow = this.setting.getMicroBootstrapFlow();\n        microBootstrapFlow.createFlow(bootstrap);\n\n        // 真实玩家连接的端口\n        final int externalCorePort = this.setting.getExternalCorePort();\n        ChannelFuture channelFuture = bootstrap.bind(externalCorePort);\n\n        try {\n            IoGameBanner.render();\n            channelFuture.channel().closeFuture().sync();\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        } finally {\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/SocketMicroBootstrapFlow.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro;\n\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.hook.internal.IdleProcessSetting;\nimport com.iohao.game.external.core.micro.PipelineContext;\nimport com.iohao.game.external.core.netty.SettingOption;\nimport com.iohao.game.external.core.netty.handler.*;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-31\n */\n@FieldDefaults(level = AccessLevel.PROTECTED)\nabstract class SocketMicroBootstrapFlow extends AbstractMicroBootstrapFlow<ServerBootstrap> {\n    @Override\n    public void channelInitializer(ServerBootstrap bootstrap) {\n        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {\n            @Override\n            protected void initChannel(SocketChannel ch) {\n                DefaultPipelineContext pipelineContext = new DefaultPipelineContext(ch, setting);\n                /*\n                 * 新建连接时的执行流程\n                 * 通常情况下，我们可以将 ChannelInitializer 内的实现划分为三部分\n                 *  1. pipelineCodec：编解码\n                 *  2. pipelineIdle：心跳相关\n                 *  3. pipelineCustom：自定义的业务编排 （大部分情况下只需要重写 pipelineCustom 就可以达到很强的扩展了）\n                 */\n                pipelineFlow(pipelineContext);\n            }\n        });\n    }\n\n    @Override\n    public void pipelineIdle(PipelineContext context) {\n        IdleProcessSetting idleProcessSetting = this.setting.getIdleProcessSetting();\n        if (Objects.isNull(idleProcessSetting)) {\n            // 如果服务器没有配置心跳相关的内容，则排除心跳数据（不做任何处理）\n            // If the server is not configured with heartbeat processing, exclude heartbeat data\n            context.addLast(\"SocketIdleExcludeHandler\", SocketIdleExcludeHandler.me());\n            return;\n        }\n\n        // netty 心跳检测\n        context.addLast(\"idleStateHandler\", new IdleStateHandler(\n                idleProcessSetting.getReaderIdleTime(),\n                idleProcessSetting.getWriterIdleTime(),\n                idleProcessSetting.getAllIdleTime(),\n                idleProcessSetting.getTimeUnit())\n        );\n\n        // 心跳响应、心跳钩子 Handler\n        SocketIdleHandler socketIdleHandler = setting.option(SettingOption.socketIdleHandler);\n        context.addLast(\"idleHandler\", socketIdleHandler);\n    }\n\n    @Override\n    public void pipelineCustom(PipelineContext context) {\n        // 日志打印（异常时）\n        if (ExternalGlobalConfig.enableLoggerHandler) {\n            context.addLast(\"SimpleLoggerHandler\", SimpleLoggerHandler.me());\n        }\n\n        // 路由存在检测\n        context.addLast(\"CmdCheckHandler\", CmdCheckHandler.me());\n\n        // 管理 UserSession 的 Handler\n        SocketUserSessionHandler socketUserSessionHandler = setting.option(SettingOption.socketUserSessionHandler);\n        context.addLast(\"UserSessionHandler\", socketUserSessionHandler);\n\n        // 路由访问验证 的 Handler\n        SocketCmdAccessAuthHandler socketCmdAccessAuthHandler = setting.option(SettingOption.socketCmdAccessAuthHandler);\n        context.addLast(\"CmdAccessAuthHandler\", socketCmdAccessAuthHandler);\n\n        // 游戏对外服路由数据缓存\n        if (Objects.nonNull(ExternalGlobalConfig.externalCmdCache)) {\n            context.addLast(\"CmdCacheHandler\", CmdCacheHandler.me());\n        }\n\n        // 负责把游戏端的请求转发给 Broker（游戏网关）的 Handler\n        SocketRequestBrokerHandler socketRequestBrokerHandler = setting.option(SettingOption.socketRequestBrokerHandler);\n        context.addLast(\"RequestBrokerHandler\", socketRequestBrokerHandler);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/TcpMicroBootstrapFlow.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro;\n\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.netty.handler.codec.TcpExternalCodec;\nimport com.iohao.game.external.core.micro.PipelineContext;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelOption;\nimport io.netty.handler.codec.LengthFieldBasedFrameDecoder;\n\n/**\n * tcp 与真实玩家连接服务器的启动流程\n *\n * @author 渔民小镇\n * @date 2023-05-28\n */\npublic class TcpMicroBootstrapFlow extends SocketMicroBootstrapFlow {\n\n    @Override\n    public void option(ServerBootstrap bootstrap) {\n        bootstrap\n                // 客户端保持活动连接\n                .childOption(ChannelOption.SO_KEEPALIVE, true)\n                /*\n                 * 是否启用心跳保活机制。在双方TCP套接字建立连接后（即都进入ESTABLISHED状态）\n                 * 并且在两个小时左右上层没有任何数据传输的情况下，这套机制才会被激活\n                 */\n                .option(ChannelOption.SO_KEEPALIVE, true)\n                /*\n                 * BACKLOG用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时，\n                 * 用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1，\n                 * 使用默认值100\n                 */\n                .option(ChannelOption.SO_BACKLOG, 100)\n                /*\n                 * 在TCP/IP协议中，无论发送多少数据，总是要在数据前面加上协议头，\n                 * 同时，对方接收到数据，也需要发送ACK表示确认。\n                 * 为了尽可能的利用网络带宽，TCP总是希望尽可能的发送足够大的数据。\n                 * 这里就涉及到一个名为Nagle的算法，该算法的目的就是为了尽可能发送大块数据，避免网络中充斥着许多小数据块。\n                 */\n                .option(ChannelOption.TCP_NODELAY, true);\n    }\n\n    @Override\n    public void pipelineCodec(PipelineContext context) {\n//        context.addLast(\"tcp-check\", new TcpProtocolSanityCheckHandler());\n\n        // 数据包长度 = 长度域的值 + lengthFieldOffset + lengthFieldLength + lengthAdjustment。\n        context.addLast(new LengthFieldBasedFrameDecoder(\n                ExternalGlobalConfig.CoreOption.packageMaxSize,\n                // 长度字段的偏移量， 从 0 开始\n                0,\n                // 字段的长度, 如果使用的是 short ，占用2位；（消息头用的 byteBuf.writeShort 来记录长度的）\n                // 字段的长度, 如果使用的是 int   ，占用4位；（消息头用的 byteBuf.writeInt   来记录长度的）\n                4,\n                // 要添加到长度字段值的补偿值：长度调整值 = 内容字段偏移量 - 长度字段偏移量 - 长度字段的字节数\n                0,\n                // 跳过的初始字节数： 跳过0位; (跳过消息头的 0 位长度)\n                0));\n\n        // tcp socket 编解码\n        context.addLast(\"codec\", new TcpExternalCodec());\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/WebSocketMicroBootstrapFlow.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.micro.PipelineContext;\nimport com.iohao.game.external.core.netty.handler.codec.WebSocketExternalCodec;\nimport com.iohao.game.external.core.netty.handler.check.HttpFallbackHandler;\nimport com.iohao.game.external.core.netty.handler.ws.WebSocketVerifyHandler;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelOption;\nimport io.netty.handler.codec.MessageToMessageCodec;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.WebSocketServerProtocolConfig;\nimport io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;\nimport io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;\n\nimport java.util.Objects;\n\n/**\n * websocket 与真实玩家连接服务器的启动流程\n *\n * @author 渔民小镇\n * @date 2023-05-31\n */\npublic class WebSocketMicroBootstrapFlow extends SocketMicroBootstrapFlow {\n    @Override\n    public void option(ServerBootstrap bootstrap) {\n        bootstrap\n                // 客户端保持活动连接\n                .childOption(ChannelOption.SO_KEEPALIVE, true)\n                /*\n                 * BACKLOG用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时，\n                 * 用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1，\n                 * 使用默认值100\n                 */\n                .option(ChannelOption.SO_BACKLOG, 100)\n\n                .childOption(ChannelOption.SO_REUSEADDR, true);\n    }\n\n    @Override\n    public void pipelineCodec(PipelineContext context) {\n        // 添加 http 相关 handler\n        this.httpHandler(context);\n\n        // 建立连接前的验证 handler\n        this.verifyHandler(context);\n\n        // 添加 websocket 相关 handler\n        this.websocketHandler(context);\n\n        // websocket 编解码\n        var externalCodec = this.createExternalCodec();\n        context.addLast(\"codec\", externalCodec);\n    }\n\n    /**\n     * 单独创建 WebSocket Codec，其他配置使用框架提供的。如果需要高度灵活定制的，可重写 pipelineCodec 方法。\n     * <pre>codec BinaryWebSocketFrame, BarMessage</pre>\n     *\n     * @return WebSocket Codec. default {@link WebSocketExternalCodec}\n     */\n    protected MessageToMessageCodec<BinaryWebSocketFrame, BarMessage> createExternalCodec() {\n        return new WebSocketExternalCodec();\n    }\n\n    protected void verifyHandler(PipelineContext context) {\n        WebSocketVerifyHandler verifyHandler = this.createVerifyHandler();\n        if (Objects.nonNull(verifyHandler)) {\n            context.addLast(\"WebSocketVerifyHandler\", verifyHandler);\n        }\n    }\n\n    protected WebSocketVerifyHandler createVerifyHandler() {\n        return null;\n    }\n\n    protected void websocketHandler(PipelineContext context) {\n        // WebSocket 数据压缩\n        context.addLast(\"compression\", new WebSocketServerCompressionHandler());\n\n        /*\n         * 处理 websocket 的解码器\n         * 1 协议包长度限制\n         * 2 验证协议url。\n         *\n         * 按照 WebSocket 规范的要求, 处理 :\n         * 1 WebSocket 升级握手, 验证GET的请求升级\n         * 2 PingWebSocketFrame\n         * 3 PongWebSocketFrame\n         * 4 CloseWebSocketFrame\n         *\n         * 移除 HTTPRequest HTTPResponse\n         */\n        WebSocketServerProtocolConfig config = WebSocketServerProtocolConfig.newBuilder()\n                // 验证 URL\n                .websocketPath(ExternalGlobalConfig.CoreOption.websocketPath)\n                // 默认数据包大小\n                .maxFramePayloadLength(ExternalGlobalConfig.CoreOption.packageMaxSize)\n                .checkStartsWith(true)\n                // 开启 WebSocket 扩展\n                .allowExtensions(true)\n                .build();\n\n        context.addLast(\"WebSocketServerProtocolHandler\", new WebSocketServerProtocolHandler(config));\n    }\n\n    protected void httpHandler(PipelineContext context) {\n        /*\n         * 将请求和应答消息解码为HTTP消息\n         * 将字节码解码为 HttpRequest,HttpContent和LastHttpContent.\n         * 并将 HttpRequest, HttpContent和LastHttpContent 编码为字节码\n         */\n        context.addLast(\"http-codec\", new HttpServerCodec());\n\n        /*\n         * 将一个 HttpMessage 和跟随它的多个 HttpContent 聚合为\n         * 单个 FullHTTPRequest 或者 FullHTTPResponse(取决于它是被用来处理请求还是响应).\n         *\n         * 安装了这个之后, ChannelPipeline 中的下一个 ChannelHandler 将只会接收\n         * 到完整的 Http 请求或响应\n         */\n        context.addLast(\"aggregator\", new HttpObjectAggregator(65536));\n\n        context.addLast(\"http-fallback\", HttpFallbackHandler.me());\n    }\n}"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/auto/EventLoopGroupThreadFactory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro.auto;\n\nimport com.iohao.game.common.kit.concurrent.DaemonThreadFactory;\n\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-18\n */\nfinal class EventLoopGroupThreadFactory {\n\n    /**\n     * 业务 ThreadFactory\n     *\n     * @return worker ThreadFactory\n     */\n    static ThreadFactory workerThreadFactory() {\n        return new DaemonThreadFactory(\"iohao.com:external-netty-server-worker\");\n    }\n\n    /**\n     * 连接 ThreadFactory\n     *\n     * @return boss ThreadFactory\n     */\n    static ThreadFactory bossThreadFactory() {\n        return new DaemonThreadFactory(\"iohao.com:external-netty-server-boss\");\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/auto/GroupChannelOption.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro.auto;\n\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.ServerChannel;\n\n/**\n * netty 核心组件. (1 连接创建线程组, 2 业务处理线程组)\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic interface GroupChannelOption {\n    /**\n     * EventLoopGroup bossGroup (连接处理组)\n     *\n     * @return EventLoopGroup\n     */\n    EventLoopGroup bossGroup();\n\n    /**\n     * EventLoopGroup workerGroup (业务处理组)\n     *\n     * @return EventLoopGroup\n     */\n    EventLoopGroup workerGroup();\n\n    /**\n     * channelClass\n     *\n     * @return channelClass\n     */\n    Class<? extends ServerChannel> channelClass();\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/auto/GroupChannelOptionForLinux.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.external.core.netty.micro.auto;\n\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.ServerChannel;\nimport io.netty.channel.epoll.EpollEventLoopGroup;\nimport io.netty.channel.epoll.EpollServerSocketChannel;\n\n/**\n * 服务端 for linux nio 处理类\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic final class GroupChannelOptionForLinux implements GroupChannelOption {\n    @Override\n    public EventLoopGroup bossGroup() {\n        return new EpollEventLoopGroup(\n                1,\n                EventLoopGroupThreadFactory.bossThreadFactory()\n        );\n    }\n\n    @Override\n    public EventLoopGroup workerGroup() {\n        int availableProcessors = Runtime.getRuntime().availableProcessors() << 1;\n\n        return new EpollEventLoopGroup(\n                availableProcessors,\n                EventLoopGroupThreadFactory.workerThreadFactory()\n        );\n    }\n\n    @Override\n    public Class<? extends ServerChannel> channelClass() {\n        return EpollServerSocketChannel.class;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/auto/GroupChannelOptionForMac.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.external.core.netty.micro.auto;\n\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.ServerChannel;\nimport io.netty.channel.kqueue.KQueueEventLoopGroup;\nimport io.netty.channel.kqueue.KQueueServerSocketChannel;\n\n/**\n * 服务端 for Mac nio 处理类\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic final class GroupChannelOptionForMac implements GroupChannelOption {\n    @Override\n    public EventLoopGroup bossGroup() {\n        return new KQueueEventLoopGroup(\n                1,\n                EventLoopGroupThreadFactory.bossThreadFactory()\n        );\n    }\n\n    @Override\n    public EventLoopGroup workerGroup() {\n        int availableProcessors = Runtime.getRuntime().availableProcessors() << 1;\n\n        return new KQueueEventLoopGroup(\n                availableProcessors,\n                EventLoopGroupThreadFactory.workerThreadFactory()\n        );\n    }\n\n    @Override\n    public Class<? extends ServerChannel> channelClass() {\n        return KQueueServerSocketChannel.class;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/auto/GroupChannelOptionForOther.java",
    "content": "/*\n * # iohao.com . 渔民小镇\n * Copyright (C) 2021 - present double joker （262610965@qq.com） . All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License..\n */\npackage com.iohao.game.external.core.netty.micro.auto;\n\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.ServerChannel;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\n\n/**\n * 服务端 for other nio 处理类\n *\n * <pre>\n * other system:\n *     Windows, Solaris\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic final class GroupChannelOptionForOther implements GroupChannelOption {\n    @Override\n    public EventLoopGroup bossGroup() {\n        return new NioEventLoopGroup(\n                1,\n                EventLoopGroupThreadFactory.bossThreadFactory()\n        );\n    }\n\n    @Override\n    public EventLoopGroup workerGroup() {\n        int availableProcessors = Runtime.getRuntime().availableProcessors() << 1;\n\n        return new NioEventLoopGroup(\n                availableProcessors,\n                EventLoopGroupThreadFactory.workerThreadFactory()\n        );\n    }\n\n    @Override\n    public Class<? extends ServerChannel> channelClass() {\n        return NioServerSocketChannel.class;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/join/SocketExternalJoinSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro.join;\n\nimport com.iohao.game.common.kit.PresentKit;\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.external.core.hook.IdleHook;\nimport com.iohao.game.external.core.hook.internal.IdleProcessSetting;\nimport com.iohao.game.external.core.micro.MicroBootstrap;\nimport com.iohao.game.external.core.micro.join.ExternalJoinSelector;\nimport com.iohao.game.external.core.netty.DefaultExternalCoreSetting;\nimport com.iohao.game.external.core.netty.SettingOption;\nimport com.iohao.game.external.core.netty.handler.SocketCmdAccessAuthHandler;\nimport com.iohao.game.external.core.netty.handler.SocketIdleHandler;\nimport com.iohao.game.external.core.netty.handler.SocketRequestBrokerHandler;\nimport com.iohao.game.external.core.netty.handler.SocketUserSessionHandler;\nimport com.iohao.game.external.core.netty.hook.DefaultSocketIdleHook;\nimport com.iohao.game.external.core.netty.micro.SocketMicroBootstrap;\nimport com.iohao.game.external.core.netty.session.SocketUserSessions;\nimport com.iohao.game.external.core.session.UserSessions;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-31\n */\nabstract class SocketExternalJoinSelector implements ExternalJoinSelector {\n    @Override\n    public void defaultSetting(ExternalCoreSetting coreSetting) {\n        DefaultExternalCoreSetting setting = (DefaultExternalCoreSetting) coreSetting;\n\n        // microBootstrap；如果开发者没有手动赋值，则根据当前连接方式生成\n        MicroBootstrap microBootstrap = setting.getMicroBootstrap();\n        PresentKit.ifNull(microBootstrap, () -> setting.setMicroBootstrap(new SocketMicroBootstrap()));\n\n        // UserSessions 管理器；如果开发者没有手动赋值，则根据当前连接方式生成\n        UserSessions<?, ?> userSessions = setting.getUserSessions();\n        PresentKit.ifNull(userSessions, () -> setting.setUserSessions(new SocketUserSessions()));\n\n        // IdleHook 心跳钩子；长连接方式开启了心跳，强制给一个心跳钩子\n        IdleProcessSetting idleProcessSetting = setting.getIdleProcessSetting();\n        if (Objects.nonNull(idleProcessSetting)) {\n            IdleHook<Object> idleHook = idleProcessSetting.getIdleHook();\n            PresentKit.ifNull(idleHook, () -> idleProcessSetting.setIdleHook(new DefaultSocketIdleHook()));\n\n            // 心跳钩子 Handler\n            setting.ifNull(SettingOption.socketIdleHandler, SocketIdleHandler::new);\n        }\n\n        // pipelineCustom Handler\n        setting.ifNull(SettingOption.socketUserSessionHandler, SocketUserSessionHandler::new);\n        setting.ifNull(SettingOption.socketCmdAccessAuthHandler, SocketCmdAccessAuthHandler::new);\n        setting.ifNull(SettingOption.socketRequestBrokerHandler, SocketRequestBrokerHandler::new);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/join/TcpExternalJoinSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro.join;\n\nimport com.iohao.game.common.kit.PresentKit;\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.micro.MicroBootstrapFlow;\nimport com.iohao.game.external.core.netty.DefaultExternalCoreSetting;\nimport com.iohao.game.external.core.netty.micro.TcpMicroBootstrapFlow;\n\n/**\n * TCP 相关\n *\n * @author 渔民小镇\n * @date 2023-05-29\n */\npublic final class TcpExternalJoinSelector extends SocketExternalJoinSelector {\n\n    @Override\n    public ExternalJoinEnum getExternalJoinEnum() {\n        return ExternalJoinEnum.TCP;\n    }\n\n    @Override\n    public void defaultSetting(ExternalCoreSetting coreSetting) {\n        super.defaultSetting(coreSetting);\n\n        DefaultExternalCoreSetting setting = (DefaultExternalCoreSetting) coreSetting;\n\n        // MicroBootstrapFlow 启动流程；如果开发者没有手动赋值，则根据当前连接方式生成\n        MicroBootstrapFlow<?> microBootstrapFlow = setting.getMicroBootstrapFlow();\n        PresentKit.ifNull(microBootstrapFlow, () -> setting.setMicroBootstrapFlow(new TcpMicroBootstrapFlow()));\n\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/join/WebSocketExternalJoinSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.micro.join;\n\nimport com.iohao.game.common.kit.PresentKit;\nimport com.iohao.game.external.core.ExternalCoreSetting;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.micro.MicroBootstrapFlow;\nimport com.iohao.game.external.core.netty.DefaultExternalCoreSetting;\nimport com.iohao.game.external.core.netty.micro.WebSocketMicroBootstrapFlow;\n\n/**\n * Websocket 相关\n *\n * @author 渔民小镇\n * @date 2023-05-29\n */\npublic final class WebSocketExternalJoinSelector extends SocketExternalJoinSelector {\n    @Override\n    public ExternalJoinEnum getExternalJoinEnum() {\n        return ExternalJoinEnum.WEBSOCKET;\n    }\n\n    @Override\n    public void defaultSetting(ExternalCoreSetting coreSetting) {\n        super.defaultSetting(coreSetting);\n\n        DefaultExternalCoreSetting setting = (DefaultExternalCoreSetting) coreSetting;\n        // MicroBootstrapFlow 启动流程；如果开发者没有手动赋值，则根据当前连接方式生成\n        MicroBootstrapFlow<?> microBootstrapFlow = setting.getMicroBootstrapFlow();\n        PresentKit.ifNull(microBootstrapFlow, () -> setting.setMicroBootstrapFlow(new WebSocketMicroBootstrapFlow()));\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 基于 Netty 的实现 <a href=\"https://iohao.github.io/game/docs/overall/external_intro\">游戏对外服</a>\n * <p>\n * 连接方式的支持、切换\n * <pre>\n *     ioGame 已提供了 TCP、WebSocket、UDP 连接方式的支持，并提供了灵活的方式来实现连接方式的切换。\n *     可以将 TCP、WebSocket、UDP 连接方式与业务代码进行无缝衔接。开发者可以用一套业务代码，无需任何改动，同时支持多种通信协议。\n *\n *     如果想要切换到不同的连接方式，只需要更改相应的枚举即可，非常简单。\n *     在不使用 ioGame 时，将连接方式从 TCP 改为 WebSocket 或 UDP 等，需要进行大量的调整和改动。\n *     然而，在 ioGame 中，实现这些转换是非常简单的。此外，不仅可以轻松切换各种连接方式，而且可以同时支持多种连接方式，并使它们在同一应用程序中共存。\n *\n *     连接方式是可扩展的，而且扩展也简单，这意味着之后如果支持了 KCP，那么将已有项目的连接方式，如 TCP、WebSocket、UDP 切换成 KCP 也是简单的。\n *\n *     需要再次强调的是，连接方式的切换对业务代码没有任何影响，无需做出任何改动即可实现连接方式的更改。\n * </pre>\n * <p>\n * 游戏对外服的核心接口\n * <pre>\n *     ExternalServer：游戏对外服，由 ExternalCore 和 ExternalBrokerClientStartup 组成的一个整体。\n *     ExternalCore： 帮助开发者屏蔽各通信框架的细节，如 Netty、mina、smart-socket 等通信框，ioGame 默认提供了基于 Netty 的实现。\n *     MicroBootstrap：真实玩家连接的服务器，服务器的创建由 MicroBootstrap 完成，MicroBootstrap 帮助开发者屏蔽连接方式的细节，如 TCP、WebSocket、UDP、KCP 等。目前已经支持 TCP、WebSocket、UDP 的连接方式，而 KCP 的连接方式也在计划内。\n *     MicroBootstrapFlow：MicroBootstrapFlow\t与真实玩家连接【真实】服务器的启动流程，专为 MicroBootstrap 服务。开发者可通过此接口对服务器做编排，编排分为：构建时、新建连接时两种。\n *\n *     MicroBootstrapFlow 接口的目的是尽可能地细化服务器创建和连接时的每个环节，以方便开发者对游戏对外服进行定制化扩展。通常情况下，开发者只需要关注重写 MicroBootstrapFlow.pipelineCustom 方法，就可以实现很强的扩展了。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-06-04\n */\npackage com.iohao.game.external.core.netty;"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/AbstractUserSession.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.session;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.external.core.session.UserChannelId;\nimport com.iohao.game.external.core.session.UserSession;\nimport com.iohao.game.external.core.session.UserSessionOption;\nimport com.iohao.game.external.core.session.UserSessionState;\nimport io.netty.channel.Channel;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-28\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PROTECTED)\nabstract class AbstractUserSession implements UserSession {\n    final AttrOptions options = new AttrOptions();\n    Channel channel;\n    /** 玩家 id */\n    long userId;\n    UserChannelId userChannelId;\n    CmdRegions cmdRegions;\n    /** 所在游戏对外服的 idHash */\n    @Setter\n    int externalClientId;\n    /** 状态 */\n    @Setter\n    UserSessionState state = UserSessionState.ACTIVE;\n\n    AbstractUserSession() {\n        // false 没有进行身份验证\n        this.option(UserSessionOption.verifyIdentity, false);\n\n        // 玩家绑定的多个游戏逻辑服记录\n        this.option(UserSessionOption.bindingLogicServerIdSet, new NonBlockingHashSet<>());\n    }\n\n    public void employ(BarMessage requestMessage) {\n        HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n        this.employ(headMetadata);\n    }\n\n    @Override\n    public void employ(HeadMetadata headMetadata) {\n        // 设置请求用户的id\n        headMetadata.setUserId(this.userId);\n        headMetadata.setSourceClientId(this.externalClientId);\n\n        this.ifPresent(UserSessionOption.externalJoin, externalJoin -> headMetadata.setStick(externalJoin.getIndex()));\n\n        if (!this.isVerifyIdentity()) {\n            // 只有没进行验证的，才给 userChannelId\n            String channelId = this.userChannelId.channelId();\n            // 一般指用户的 channelId （来源于对外服的 channel 长连接）\n            headMetadata.setChannelId(channelId);\n        }\n\n        // 设置用户绑定的游戏逻辑服 id\n        this.ifPresent(UserSessionOption.bindingLogicServerIdArray, headMetadata::setBindingLogicServerIds);\n\n        // 如果 headMetadata 的 attachmentData 不为 null，通常是开发者在其他地方给 attachmentData 设置了值，框架就不管了。\n        if (Objects.isNull(headMetadata.getAttachmentData())) {\n            // 将 UserSession attachment 的值设置到 HeadMetadata attachmentData 中\n            this.ifPresent(UserSessionOption.attachment, headMetadata::setAttachmentData);\n        }\n    }\n\n    @Override\n    public void setUserId(long userId) {\n        this.userId = userId;\n        this.option(UserSessionOption.verifyIdentity, true);\n    }\n\n    @Override\n    public boolean isVerifyIdentity() {\n        return this.optionValue(UserSessionOption.verifyIdentity, false);\n    }\n\n    @Override\n    public boolean isActive() {\n        return true;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof AbstractUserSession that)) {\n            return false;\n        }\n\n        return Objects.equals(userChannelId, that.userChannelId);\n    }\n\n    @Override\n    public int hashCode() {\n        return userChannelId.hashCode();\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/AbstractUserSessions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.session;\n\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.hook.UserHook;\nimport com.iohao.game.external.core.session.UserChannelId;\nimport com.iohao.game.external.core.session.UserSession;\nimport com.iohao.game.external.core.session.UserSessionOption;\nimport com.iohao.game.external.core.session.UserSessions;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\nimport org.jctools.maps.NonBlockingHashMapLong;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * UserSessions 抽象父类\n *\n * @author 渔民小镇\n * @date 2023-05-28\n */\n@FieldDefaults(level = AccessLevel.PROTECTED)\nabstract class AbstractUserSessions<ChannelHandlerContext, Session extends UserSession>\n        implements UserSessions<ChannelHandlerContext, Session>, CmdRegionsAware {\n\n    @Getter\n    final AttrOptions options = new AttrOptions();\n    /**\n     * key : 玩家 id\n     * value : UserSession\n     */\n    final NonBlockingHashMapLong<Session> userIdMap = new NonBlockingHashMapLong<>();\n\n    /**\n     * key : userChannelId\n     * value : UserSession\n     */\n    final Map<UserChannelId, Session> userChannelIdMap = new NonBlockingHashMap<>();\n\n    @Setter\n    CmdRegions cmdRegions;\n\n    @Setter\n    UserHook userHook;\n\n    @Override\n    public boolean existUserSession(long userId) {\n        return this.userIdMap.containsKey(userId);\n    }\n\n    @Override\n    public Session getUserSession(long userId) {\n        return this.userIdMap.get(userId);\n    }\n\n    @Override\n    public Session getUserSession(UserChannelId userChannelId) {\n        return this.userChannelIdMap.get(userChannelId);\n    }\n\n    @Override\n    public void removeUserSession(long userId, Object msg) {\n        this.ifPresent(userId, userSession -> {\n            ChannelFuture channelFuture = userSession.writeAndFlush(msg);\n            channelFuture.addListener((ChannelFutureListener) future -> {\n                // 回调 UserSessions 中移除对应的玩家\n                this.removeUserSession(userSession);\n            });\n        });\n    }\n\n    @Override\n    public void forEach(Consumer<Session> consumer) {\n        this.userChannelIdMap.values().forEach(consumer);\n    }\n\n    /**\n     * 上线通知。注意：只有进行身份验证通过的，才会触发此方法\n     *\n     * @param userSession userSession\n     */\n    void userHookInto(UserSession userSession) {\n        if (Objects.isNull(this.userHook)) {\n            return;\n        }\n\n        this.userHook.into(userSession);\n    }\n\n    /**\n     * 离线通知。注意：只有进行身份验证通过的，才会触发此方法\n     *\n     * @param userSession userSession\n     */\n    void userHookQuit(UserSession userSession) {\n        if (Objects.isNull(userHook)) {\n            return;\n        }\n\n        this.userHook.quit(userSession);\n    }\n\n    void settingDefault(UserSession userSession) {\n        // 保存连接方式\n        ExternalJoinEnum externalJoinEnum = this.option(UserSessionOption.externalJoin);\n        userSession.option(UserSessionOption.externalJoin, externalJoinEnum);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/SocketUserSession.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.session;\n\nimport com.iohao.game.external.core.session.UserChannelId;\nimport com.iohao.game.external.core.session.UserSessionOption;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\n\nimport java.net.InetSocketAddress;\nimport java.util.Objects;\n\n/**\n * 长连接的 UserSession\n * <pre>\n *     tcp、websocket\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\npublic final class SocketUserSession extends AbstractUserSession {\n\n    public SocketUserSession(Channel channel) {\n        this.channel = channel;\n        // asLongText 使用空间换时间的策略；因为在登录后 channelId 将不会用于传输\n        String channelId = this.channel.id().asLongText();\n        this.userChannelId = new UserChannelId(channelId);\n    }\n\n    @Override\n    public boolean isActive() {\n        return Objects.nonNull(this.channel) && this.channel.isActive();\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public ChannelFuture writeAndFlush(Object message) {\n        return this.channel.writeAndFlush(message);\n    }\n\n    @Override\n    public String getIp() {\n\n        // 优先拿玩家真实 ip\n        String realIp = this.option(UserSessionOption.realIp);\n\n        if (realIp.isEmpty()) {\n            InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();\n            return inetSocketAddress.getHostString();\n        }\n\n        return realIp;\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/SocketUserSessions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.session;\n\n\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport com.iohao.game.external.core.session.UserChannelId;\nimport com.iohao.game.external.core.session.UserSessionState;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.channel.group.DefaultChannelGroup;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.concurrent.GlobalEventExecutor;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\n\n/**\n * tcp、websocket 使用的 session 管理器\n * <pre>\n *     具备长连接的 session 管理器\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-18\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class SocketUserSessions extends AbstractUserSessions<ChannelHandlerContext, SocketUserSession> {\n    /** 用户 session，与channel是 1:1 的关系 */\n    static final AttributeKey<SocketUserSession> userSessionKey = AttributeKey.valueOf(\"userSession\");\n\n    final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);\n\n    @Override\n    public SocketUserSession add(ChannelHandlerContext channelHandlerContext) {\n\n        Channel channel = channelHandlerContext.channel();\n\n        SocketUserSession userSession = new SocketUserSession(channel);\n        userSession.cmdRegions = this.cmdRegions;\n\n        // channel 中也保存 UserSession 的引用\n        channel.attr(SocketUserSessions.userSessionKey).set(userSession);\n\n        UserChannelId userChannelId = userSession.getUserChannelId();\n        this.userChannelIdMap.putIfAbsent(userChannelId, userSession);\n        this.channelGroup.add(channel);\n\n        this.settingDefault(userSession);\n\n        return userSession;\n    }\n\n    @Override\n    public SocketUserSession getUserSession(ChannelHandlerContext channelHandlerContext) {\n        Channel channel = channelHandlerContext.channel();\n        return channel.attr(userSessionKey).get();\n    }\n\n    @Override\n    public boolean settingUserId(UserChannelId userChannelId, long userId) {\n\n        SocketUserSession userSession = this.getUserSession(userChannelId);\n        if (Objects.isNull(userSession)) {\n            return false;\n        }\n\n        if (Boolean.FALSE.equals(userSession.isActive())) {\n            removeUserSession(userSession);\n            return false;\n        }\n\n        userSession.setUserId(userId);\n\n        this.userIdMap.put(userId, userSession);\n\n        // 上线通知\n        if (userSession.isVerifyIdentity()) {\n            this.userHookInto(userSession);\n        }\n\n        return true;\n    }\n\n    @Override\n    public void removeUserSession(SocketUserSession userSession) {\n\n        if (Objects.isNull(userSession)) {\n            return;\n        }\n\n        long userId = userSession.getUserId();\n        ExecutorRegionKit.getSimpleThreadExecutor(userId)\n                .executeTry(() -> internalRemoveUserSession(userSession));\n    }\n\n    private void internalRemoveUserSession(SocketUserSession userSession) {\n        if (userSession.getState() == UserSessionState.DEAD) {\n            removeUserSessionMap(userSession);\n            return;\n        }\n\n        if (userSession.getState() == UserSessionState.ACTIVE && userSession.isVerifyIdentity()) {\n            userSession.setState(UserSessionState.DEAD);\n            this.userHookQuit(userSession);\n        }\n\n        removeUserSessionMap(userSession);\n\n        // 关闭用户的连接\n        userSession.getChannel().close();\n    }\n\n    private void removeUserSessionMap(SocketUserSession userSession) {\n        long userId = userSession.getUserId();\n        // #334，kv 与预期一致时才移除。（类似联合主键）\n        this.userIdMap.remove(userId, userSession);\n\n        UserChannelId userChannelId = userSession.getUserChannelId();\n        if (Objects.nonNull(userChannelId)) {\n            this.userChannelIdMap.remove(userChannelId);\n        }\n\n        Channel channel = userSession.getChannel();\n        if (Objects.nonNull(channel)) {\n            this.channelGroup.remove(channel);\n        }\n    }\n\n    @Override\n    public int countOnline() {\n        return this.channelGroup.size();\n    }\n\n    @Override\n    public void broadcast(Object msg) {\n        this.channelGroup.writeAndFlush(msg);\n    }\n}\n"
  },
  {
    "path": "external/external-netty/src/main/resources/META-INF/services/com.iohao.game.external.core.micro.join.ExternalJoinSelector",
    "content": "com.iohao.game.external.core.netty.micro.join.TcpExternalJoinSelector\ncom.iohao.game.external.core.netty.micro.join.WebSocketExternalJoinSelector"
  },
  {
    "path": "history/changeLog_ioGame17.md",
    "content": "# ioGame17 - 更新日志\n\n\n## 2024\n### 2024-01-03 - v17.1.61\nhttps://github.com/iohao/ioGame/releases/tag/17.1.61\n\n**版本更新汇总**\n> 1. [#223](https://github.com/iohao/ioGame/issues/223) 一天内 action 各小时的调用统计插件\n\n<hr/>\n\n## 2023\n### 2023-12-10 - v17.1.60\nhttps://github.com/iohao/ioGame/releases/tag/17.1.60\n\n**版本更新汇总**\n> 1. [#227](https://github.com/iohao/ioGame/issues/227) 增加调度、定时器相关便捷工具，使用 HashedWheelTimer 来模拟 ScheduledExecutorService 调度\n> 2. [#225](https://github.com/iohao/ioGame/issues/225) 新增插件 - 业务线程监控\n> 3. 废弃 InternalKit，使用 TaskKit 代替\n> 4. HeadMetadata 新增 cloneAll 方法\n> 5. 加 ThreadExecutorRegion 线程执行器管理域接口\n\n### 2023-11-20 - v17.1.59\nhttps://github.com/iohao/ioGame/releases/tag/17.1.59\n\n**版本更新汇总**\n> 1. TImeKit 新加时间更新策略，开发者可设置时间更新策略。\n> 2. RandomKit 功能增强，随机得到数组中的一个元素。\n> 3. 移除 SocketUserSessionHandler exceptionCaught 的日志打印。\n> 4. [#221](https://github.com/iohao/ioGame/issues/221) 新增 action 调用统计插件 StatisticActionInOut\n> 5. FlowContext 增加 inOutStartTime 和 getInOutTime 方法，用于记录插件的开始执行时间和结束时所消耗的时间。\n\n### 2023-11-02 - v17.1.58\nhttps://github.com/iohao/ioGame/releases/tag/17.1.58\n\n**版本更新汇总**\n> 1. [#194 ](https://github.com/iohao/ioGame/issues/194) 可能在 SpringBoot 集成 light-domain-event 时，启动报 java.lang.ClassNotFoundException\n> 2. [#198 ](https://github.com/iohao/ioGame/issues/198)\n\n### 2023-09-06 - v17.1.55\nhttps://github.com/iohao/ioGame/releases/tag/17.1.55\n\n**版本更新汇总**\n> 1. [#186](https://github.com/iohao/ioGame/issues/186) 增强 ProtoDataCodec\n\n### 2023-08-18 - v17.1.54\nhttps://github.com/iohao/ioGame/releases/tag/17.1.54\n\n**版本更新汇总**\n> 1. [#174](https://github.com/iohao/ioGame/pull/174) **fix action 交给容器管理时，实例化两次的问题**\n> 2. 游戏对外服新增 HttpRealIpHandler，用于获取玩家真实 ip 支持\n> 3. 压测&模拟客户端请求模块，新增模块名标识\n\n```java\npublic class BagInputCommandRegion extends AbstractInputCommandRegion {\n    @Override\n    public void initInputCommand() {\n        this.inputCommandCreate.cmd = BagCmd.cmd;\n        this.inputCommandCreate.cmdName = \"Bag\";\n    }\n}\n```\n\n### 2023-08-07 - v17.1.52\nhttps://github.com/iohao/ioGame/releases/tag/17.1.52\n\n**版本更新汇总**\n1. [#172](https://github.com/iohao/ioGame/issues/172) 新增 webSocket token 鉴权、校验支持\n2. 移除 light-log 模块，统一使用 lombok slf4j 相关注解\n3. 模拟客户端新增 SplitParam，方便模拟测试时，解析控制台输入参数的获取\n\n```java\nprivate void useRequest() {\n    InputRequestData inputRequestData = () -> {\n        ScannerKit.log(() -> log.info(\"输入需要使用的背包物品，格式 [背包物品id-数量]\"));\n        String inputType = ScannerKit.nextLine(\"1-1\");\n\n        SplitParam param = new SplitParam(inputType);\n        // 得到下标 0 的值\n        String id = param.getString(0);\n        // 得到下标 1 的值，如果值不存在，则使用默认的 1 代替\n        int quantity = param.getInt(1, 1);\n    };\n}\n```\n\n### 2023-08-01 - v17.1.50\nhttps://github.com/iohao/ioGame/releases/tag/17.1.50\n\n**版本更新汇总**\n> 1. DebugInout 在自定义 FlowContext 时的打印优化。\n> 2. CmdInfo 新增 of 系列方法，用于代替 getCmdInfo 系列方法\n> 3. 异常机制接口 MsgExceptionInfo，新增方法 assertTrueThrows、assertNonNull\n\n### 2023-07-28 - v17.1.48\n> 1. 文档生成增强，增加 action 参数注释说明.\n> 2. 文档生成增强，返回值注释说明.\n> 3. fix 在 pom 中引入本地 jar 时，文档解析的错误。\n\n### 2023-07-21 - v17.1.47\n> 1. [#160](https://github.com/iohao/ioGame/issues/160) 增加压测&模拟客户端请求模块\n\n### 2023-07-07 - v17.1.45\n> 1. [#157](https://github.com/iohao/ioGame/issues/157) fix 默认心跳钩子问题\n> 2. [#122](https://github.com/iohao/ioGame/issues/122) 同进程亲和性\n\n### 2023-06-14 - v17.1.44\n> 1. [#133](https://github.com/iohao/ioGame/issues/133) 向指定对外服上的用户广播数据\n> 2. [#131](https://github.com/iohao/ioGame/issues/131) 获取指定对外服上数据的接口\n\n### 2023-06-08 - v17.1.43\n> 1. [#115](https://github.com/iohao/ioGame/issues/115) 游戏对外服增加路由是否存在检测\n> 2. [#114](https://github.com/iohao/ioGame/issues/114) 支持玩家与多个游戏逻辑服的动态绑定\n> 3. [#113](https://github.com/iohao/ioGame/issues/113) 新版本游戏对外服\n\n### 2023-05-29 - v17.1.42\n> 1. [#127](https://github.com/iohao/ioGame/issues/127) DebugInOut，可限制某些 action 不输出 log\n> 2. [#132](https://github.com/iohao/ioGame/issues/132) 集群重启后组网异常\n\n### 2023-05-09 - v17.1.40\n> 1. [#99](https://github.com/iohao/ioGame/issues/99) 增加 msgId 特性，只在 request/response 通讯方式下生效\n> 2. [#102](https://github.com/iohao/ioGame/issues/102) 业务框架 BarSkeleton 类增加动态属性，方便扩展.\n> 3. [#111](https://github.com/iohao/ioGame/issues/111) 新增文档解析、文档生成的控制选项\n> 4. [#103](https://github.com/iohao/ioGame/issues/103) 业务框架新增 Runner 机制，增强扩展性、规范性\n> 5. [#104](https://github.com/iohao/ioGame/issues/104) 新增实验性特性-脉冲通讯方式\n\n### 2023-04-18 - v17.1.38\n> 1. [#46](https://github.com/iohao/ioGame/issues/46) action 业务参数与返回值增加 List 支持\n> 2. [#74](https://github.com/iohao/ioGame/issues/74) action 返回值增加 byte[] 扩展\n> 3. [#79](https://github.com/iohao/ioGame/issues/79) light-jprotobuf 模块支持枚举\n> 4. [#84](https://github.com/iohao/ioGame/issues/84) 生成proto文件时,内容的顺序总是产生变化\n> 5. 将 IoGameGlobalSetting.me() 方法标记为废弃的，将 IoGameGlobalSetting 内的方法改为静态的。\n\n### 2023-04-03 - v17.1.37\n> 1. [#77](https://github.com/iohao/ioGame/issues/77) RequestMessageClientProcessorHook 提供新的默认实现\n\n### 2023-03-27 - v17.1.35\n> 1. [#62](https://github.com/iohao/ioGame/issues/62) RequestMessageClientProcessor 中的 FlowContext 增加路由、响应对象、用户id 相关的属性设置。\n> 2. 领域事件部分代码整理\n\n### 2023-03-02 - v17.1.34\n> 1. [#47](https://github.com/iohao/ioGame/issues/47) 增加拒绝外部直接访问 action 的路由权限\n> 2. [#45](https://github.com/iohao/ioGame/issues/45) 游戏对外服独立 UserSession 管理部份逻辑，做成单独的 UserSessionHandler\n> 3. [#54](https://github.com/iohao/ioGame/issues/54) 动态属性接口增加消费操作\n\n### 2023-02-13 - v17.1.33\n> 1. [#38](https://github.com/iohao/ioGame/issues/38) 新增业务参数自动装箱、拆箱基础类型-boolean\n> 2. [#39](https://github.com/iohao/ioGame/issues/39) 新增业务参数自动装箱、拆箱\"基础\"类型-String\n> 3. [#42](https://github.com/iohao/ioGame/issues/42) 新增业务参数自动装箱、拆箱基础类型-int\n> 4. [#43](https://github.com/iohao/ioGame/issues/43) 新增业务参数自动装箱、拆箱基础类型-long\n> 5. 修复 ExternalServerBuilder.channelPipelineHook 设置业务编排钩子接口被默认实现覆盖的问题\n\n### 2023-02-03 - v17.1.31\n> 1. [#34](https://github.com/iohao/ioGame/issues/34) 日志模块增加\n> 2. [#36](https://github.com/iohao/ioGame/issues/36) 增加 Banner 打印版本、内存占用、启动耗时等信息。\n> 3. [#37](https://github.com/iohao/ioGame/issues/37) 缩小打包后的包体大小，移除一些第三方库\n> 4. MethodParsers 增加 action 参数解析器的默认设置\n> 5. 业务参数自动装箱、拆箱基础类型增强\n> 6. 修复广播的数据为空时，广播虽然是成功的，但是打印广播日志报错的问题\n\n\n### 2023-01-14 - v17.1.29\n> 1. 修复文档生成时的路径问题\n\n<hr/>\n\n## 2022\n\n### 2022-12-29 - v17.1.28\n> 1. 增加版本标识，并在 DebugInOut 插件中显示的打印。\n\n### 2022-12-14 - v17.1.27\n> 1. 命令解析器与源码文档逻辑分离。\n> 2. 优化命令对象，减少 action 类的实例化对象\n> 3. [#30](https://github.com/iohao/ioGame/issues/30) 简化元附加信息的使用\n\n### 2022-12-06 - v17.1.26\n> 1. [#27](https://github.com/iohao/ioGame/issues/27) 业务框架与网络通信框架解耦\n> 2. [#28](https://github.com/iohao/ioGame/issues/28) 新增 ChannelPipelineHook netty 业务编排的处理器钩子接口，用于游戏对外服。\n\n### 2022-11-29 - v17.1.25\n> 1. ActionCommandInfoBuilder 改名为 ActionCommandParser 命令解析器\n> 2. BarSkeletonBuilderParamConfig 类的方法名变更\n> 3. 支持多种协议：protobuf、json，并支持可扩展\n\n### 2022-11-14 - v17.1.23\n> 1. BrokerServerBuilder 游戏网关构建器中增加移除 UserProcessor 的方法\n> 2. 提供 UserProcessor 用户线程池设置策略。分离 IO 线程池与用户线程池，这样服务器可以在同一时间内处理更多的请求。\n> 3. 修复动态绑定游戏逻辑服不能取消，不能路由到其他游戏逻辑服的问题\n> 4. 废弃 BrokerGlobalConfig ，由 IoGameGlobalConfig 代替。\n\n\n### 2022-10-31 - v17.1.21\n> 1. 移除游戏网关的 Spring 依赖，之前使用了 Spring 的日志彩色打印，改为使用 logback 提供的。\n> 2. 现在 ioGame 的 JSR 校验支持 Jakarta 和 Javax 两种，基于 java SPI 实现\n> 3. SimpleRunOne 中 startup 优化\n> 4. 框架提供 cmd 路由对应的响应数据类型信息，方便后续做\"模拟客户端\" 支持\n\n### 2022-09-26 - v17.1.20\n> 1. light-timer-task，将任务延时器的任务数量默认值 2_000 --> 10_000\n> 2. 将 MsgException 修改为运行时异常；下面两个 action 的业务逻辑处理是等价的，其中一个显示的声明了 throws。\n> 3. JSR380 新增参数分组校验，分组校验在 web 开发中很常见，现在 ioGame 也支持这一特性了。\n> 4. 修复：light-profile getInt getBool 无法获取数据\n> 5. 移除 HeadMetadata 类的 cmd、subCmd 属性，进一步减少传输，因为 cmd、subCmd 属性数据可以通过 cmdMerge 计算出来；\n\n假设你的游戏请求量可以达到每秒 100W 次时，至少可为服务器每秒节省大约\n8 * 1000000 / 1024 / 1024 = 7.63 MB 的传输量；\n当请通讯方式是 请求/响应 类型时，每秒节省大约 7.63 * 2 = 15.26 MB;\n\n### 2022-09-08 - v17.1.18\n领域事件变更默认配置\nDomainEventContextParam.producerType 由 ProducerType.SINGLE 改为 ProducerType.MULTI\nDomainEventContextParam.waitStrategy 由 BlockingWaitStrategy 改为 LiteBlockingWaitStrategy\n\n### 2022-08-29 - v17.1.17\n> 1. 新增集群日志开关 BrokerGlobalConfig.brokerClusterLog，默认为 true\n> 2. 增强顶号强制下线的问题，在发送强制离线时，需要先给个客户端发个错误码。\n> 3. 修复 JSR380 验证相关缺少判断\n\n### 2022-08-23 - v17.1.13\n> 1. 游戏逻辑服与同类型逻辑服通信，超时问题。新增 SyncRequestMessageClientProcessor、SyncRequestMessage 来处理超业务逻辑时间比较长的业务。\n> 2. 支持自定义 FlowContext\n\n### 2022-08-16 - v17.1.10\n> 1. 逻辑服务之间通信相关的扩展增强方式\n> 2. 游戏网关的扩展增强 - 元素选择器生产工厂\n> 3. 游戏对外服、动态绑定逻辑服节点相关\n\n### 2022-08-08 - v17.1.9\n> 1. UserHook quit方法中调用ExternalKit.requestGateway(userSession, requestMessage); 抛出异常\n> 2. 断言抛异常没有带异常信息\n> 3. 顶号强制下线时，发送一个错误码给连接端\n> 4. 框架内置可选模块 light-jprotobuf\n\n### 2022-07-28 - v17.1.4\n> 1. 新增：【游戏逻辑服】访问多个【游戏对外服】的上下文。\n> 2. 新增：元信息 - 附加信息 - 在 action 中得到附加数据\n> 3. 新增：严格登录，路由访问权限的控制\n\n### 2022-07-19 - v17.1.3\n> 1. 增强：相同路由可以用在 action 与广播上；当 action 返回值为 void 时，可以复用路由来作为广播的响应路由。\n> 2. 广播推送添加新成员，新增顺序特性成员 BroadcastOrderContext 可以确保消息是严格顺序的。\n\n### 2022-07-11 - v17.1.2\n> 1. InvokeModuleContext，新增无参请求方法，单个逻辑服与单个逻辑服通信请求 invokeModuleMessage\n> 2. BarMessage.dataClass 字段，由 Class 类型改为 String类型\n> 3. 新增综合示例\n> 4. 移除 JSR303 相关，使用符合 JSR380 标准的校验。\n\n### 2022-07-06 - v17.1.1\nioGame 上传到中央仓库中。\n\nioGame 版本规则 x.y.z\n- x 表示当前使用的 JDK 版本\n- y 表示 ioGame API 变更版本 （但基本上不会变动，常规下是变动 x 才会变动 API）\n- z 表示 ioGame 新特性、新功能、新模块、bugfix 相关\n\nioGame 的 x 会跟着最新的 JDK LTS 版本来走的，目的是确保 ioGame 的 API 不会发生很大的变化。\n为了保持新活力与接受新鲜事物， ioGame 基本会用上最新的 JDK LTS，也就是说，下一个 x 将会是 21。\nx 一般延后 2~4 个季度，给开发者一个缓冲，即下一个 JDK LTS 出来后，那么 ioGame 的 x 会在 2~4 个季度后跟上。\n\n\n### 2022-06\n> 1. 新增 ClientProcessorHooks 业务框架处理请求时，开发者可以自定义业务线程编排\n> 2. 将模块之间的访问独立一个接口\n\n### 2022-05\n> - 新增 request/multiple_response 通讯模型。\n> - 支持对外服的玩家绑定指定的游戏逻辑服（可以做到动态分配逻辑服资源）。\n> - DebugInOut 新增设置最小触发打印时间、新增逻辑服id、逻辑服类型的打印信息。\n> - action 的返回值支持 null。\n> - 每个逻辑服都可以独立进程部署\n> - 新增 Broker 概念与 BrokerClient 概念\n> - 新增 Broker 集群，集群使用 gossip 协议\n> - 新增 DataCodec 开发者可以自定义业务参数的编解码\n> - 支持逻辑服（BrokerClient）与游戏网关（Broker）的数量扩展，并能很好的进行负载均衡。\n> - 新增 分布式锁-基于 Redisson 的简单实现\n\n### 2022-04\n> - 新增 u3d、cocos 连接示例 和 tcp socket 的连接示例\n> - 独立 bolt 网络通信框架到单独目录 net-bolt。\n\n### 2022-03\n> - 增加房间模块\n> - 新增 Spring 集成\n> - DefaultActionFactoryBean 新增创建 action 混合特性\n> - 新增 UserSession\n> - FlowContext 新增动态属性\n> - 新增心跳相关设置 IdleProcessSetting\n> - 新增 ExternalJoinEnum\n> - 新增用户钩子接口 UserHook 上线时、下线时会触发\n> - 简化对外服务器 - 构建器 ExternalServerBuilder\n> - 业务框架新增 InOutInfo 管理插件相关\n> - 新增简单的启动器 SimpleRunOne\n\n### 2022-02\n> - BarSkeletonBuilderParamConfig 新增构建 BarSkeletonBuilder 方法\n> - 游戏框架对外服默认连接协议改为 WebSocket\n> - 编写游戏框架文档\n> - 新增 游戏（错误码）文档的生成\n> - 新增 游戏（异常信息）文档的生成\n> - 新增 BarSkeletonBuilderParamConfig 构建参数的配置\n> - 新增动态属性 AttrOptionDynamic 接口\n\n### 2022-01\n> - 修复广播时的 bug （在逻辑服传输数据到网关时，response.data 对象如果没有实现 Serializable 会异常）。\n> - 新增 light-jprotobuf 简化 jprotobuf 的编写方式。\n> - 业务框架支持文档生成，java 代码既文档。\n> - DebugInOut 可定位代码行数。\n> - 新增路由错误码： 一般是客户端请求了不存在的路由引起的\n> - 新增 websocket  编解码\n> - 增加 websocket 数据压缩扩展\n> - 网关新增路由检测\n> - 业务框架 BarMessage 增加 rpcCommandType 字段\n> - 新增 request/void 通讯模型。\n> - DebugInOut 日志可以支持 JSR 303、JSR 349、JSR 380 验证规范 的日志.\n> - 优化业务框架: Flow 流程，在开启业务参数验证规范功能时，业务参数如果验证不通过，则直接响应带有错误码的消息给调用端。\n> - 业务框架支持 JSR 303、JSR 349、JSR 380 验证规范。\n> - 业务框架新增 FlowContext 上下文，生命周期存在于当前执行流程。\n> - 对外服新增接收并处理 来自网关的广播消息。\n> - 业务框架加强规范异常处理\n> - 提供异常全局统一处理规范\n> - 领域事件新增默认异常处理\n> - 游戏对外服务器 支持 websocket\n> - 业务框架支持 proto\n> - 编写对外服务器，对外服务器连接到网关。\n> - 增加加载项 BootConfig\n> - 动态属性\n\n<hr/>\n\n## 2021\n\n### 2021-12\n新增领域事件模块\n初始化项目，编写业务框架\n"
  },
  {
    "path": "net-bolt/README.md",
    "content": "tree -L 1\n\n```text\n.\n├── bolt-broker-server ##broker 游戏网关\n├── bolt-client ## broker client 游戏逻辑服\n└── bolt-core ## 游戏网关和逻辑服 ，bolt 相关 core 包\n```\n\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/README.md",
    "content": "broker server (游戏网关)\n\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>bolt-broker-server</artifactId>\n    <name>bolt-broker-server for ioGame</name>\n\n    <properties>\n        <!-- https://mvnrepository.com/artifact/io.scalecube/scalecube-transport-netty -->\n        <scalecube.version>2.6.17</scalecube.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>bolt-core</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/io.scalecube/scalecube-cluster -->\n        <dependency>\n            <groupId>io.scalecube</groupId>\n            <artifactId>scalecube-cluster</artifactId>\n            <version>${scalecube.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/io.scalecube/scalecube-transport-netty -->\n        <dependency>\n            <groupId>io.scalecube</groupId>\n            <artifactId>scalecube-transport-netty</artifactId>\n            <version>${scalecube.version}</version>\n            <exclusions>\n                <exclusion>\n                    <artifactId>*</artifactId>\n                    <groupId>io.netty</groupId>\n                </exclusion>\n                <exclusion>\n                    <groupId>io.projectreactor.netty</groupId>\n                    <artifactId>reactor-netty</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <dependency>\n            <groupId>io.projectreactor.netty</groupId>\n            <artifactId>reactor-netty</artifactId>\n            <!-- https://mvnrepository.com/artifact/io.projectreactor.netty/reactor-netty -->\n            <version>1.2.7</version>\n        </dependency>\n\n    </dependencies>\n\n</project>"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/Broker.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport lombok.AccessLevel;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * broker （游戏网关）\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Data\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Accessors(chain = true)\npublic class Broker implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -221407958089075449L;\n    /**\n     * 服务器唯一标识\n     * <pre>\n     *     如果没设置，会随机分配一个\n     *\n     *     逻辑服的模块id，标记不同的逻辑服模块。\n     *     开发者随意定义，只要确保每个逻辑服的模块 id 不相同就可以\n     * </pre>\n     */\n    String id;\n    final long startedAt;\n    /** broker （游戏网关） ip */\n    final String ip;\n    /** broker （游戏网关） port */\n    int port;\n    /** broker （游戏网关）地址  格式 ip:port */\n    String brokerAddress;\n    /** cluster 格式 ip:port */\n    String clusterAddress;\n\n    public Broker(String ip) {\n        this.ip = ip;\n        startedAt = System.currentTimeMillis();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/BrokerCluster.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * broker 集群相关\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@UtilityClass\npublic class BrokerCluster {\n    public BrokerClusterManagerBuilder newBrokerClusterManagerBuilder() {\n        return new BrokerClusterManagerBuilder();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/BrokerClusterManager.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport com.iohao.game.bolt.broker.core.message.BrokerClusterMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.NetworkKit;\nimport io.scalecube.cluster.Cluster;\nimport io.scalecube.cluster.ClusterImpl;\nimport io.scalecube.net.Address;\nimport io.scalecube.transport.netty.tcp.TcpTransportFactory;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Bolt Broker Manager 集群\n * <pre>\n *     gossip\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Slf4j(topic = IoGameLogName.ClusterTopic)\npublic final class BrokerClusterManager {\n    /** broker （游戏网关）唯一 id */\n    String brokerId;\n    /** broker 端口（游戏网关端口） */\n    int port;\n    /** Gossip listen port 监听端口 */\n    int gossipListenPort;\n\n    /**\n     * 种子节点地址\n     * <pre>\n     *     格式： ip:port\n     * </pre>\n     */\n    List<String> seedAddress;\n\n    Cluster cluster;\n\n    ClusterMessageListener clusterMessageListener;\n\n    String name;\n\n    BrokerClusterMessageHandler messageHandler;\n\n    BrokerClusterManager() {\n    }\n\n    public void start() {\n        final String localIp = NetworkKit.LOCAL_IP;\n        Broker localBroker = getLocalBroker(localIp);\n\n        this.name = String.format(\"ioGameCluster-%d-%d-%s\", port, gossipListenPort, localIp);\n        this.messageHandler = new BrokerClusterMessageHandler(name, localBroker, clusterMessageListener);\n\n        // 种子节点地址\n        List<Address> seedMemberAddress = this.listSeedMemberAddress();\n\n        this.cluster = new ClusterImpl()\n                .config(options -> options\n                        .memberAlias(name)\n                        .metadata(new BrokerClusterMetadata(name, localBroker))\n                        .externalHost(localIp)\n                        // externalPort是一个容器环境的配置属性，它被设置为向 scalecube 集群发布一个映射到 scalecube 传输侦听端口。\n                        .externalPort(gossipListenPort)\n                )\n                // 种子成员地址\n                .membership(membershipConfig -> membershipConfig\n                        // 种子节点地址\n                        .seedMembers(seedMemberAddress)\n                        // 时间间隔\n                        .syncInterval(5_000)\n                )\n                .handler(messageHandler)\n                .transportFactory(TcpTransportFactory::new)\n                .transport(transportConfig -> transportConfig.port(gossipListenPort))\n                .startAwait();\n\n        Map<String, Broker> brokers = this.messageHandler.brokers;\n        brokers.put(localBroker.getClusterAddress(), localBroker);\n    }\n\n    public BrokerClusterMessage getBrokerClusterMessage() {\n        return this.messageHandler.getBrokerClusterMessage();\n    }\n\n    private Broker getLocalBroker(String localIp) {\n        String clusterAddress = localIp + \":\" + this.gossipListenPort;\n        String brokerAddress = localIp + \":\" + this.port;\n        return new Broker(localIp)\n                .setId(this.brokerId)\n                .setPort(this.port)\n                .setBrokerAddress(brokerAddress)\n                .setClusterAddress(clusterAddress);\n    }\n\n    private List<Address> listSeedMemberAddress() {\n        return this.seedAddress\n                .stream()\n                .map(Address::from)\n                .toList();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/BrokerClusterManagerBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.cluster.ClusterMessageListenerImpl;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * bolt broker server 集群的管理 构建器\n * <pre>\n *     构建器创建\n *     {@link BrokerCluster#newBrokerClusterManagerBuilder()}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n\n@Setter\n@Accessors(fluent = true)\n@Slf4j(topic = IoGameLogName.ClusterTopic)\npublic class BrokerClusterManagerBuilder {\n    /**\n     * 种子节点地址\n     * <pre>\n     *     格式： ip:port\n     *\n     *     -- 生产环境的建议 --\n     *     注意，在生产上建议一台物理机配置一个 broker （游戏网关）\n     *     一个 broker 就是一个节点\n     *     比如配置三台机器，端口可以使用同样的端口，假设三台机器的 ip 分别是:\n     *     192.168.1.10:30056\n     *     192.168.1.11:30056\n     *     192.168.1.12:30056\n     *\n     *\n     *     -- 注意这里如果没设置，构建器会给一个默认值用于测试 --\n     *     这里配置写死是方便在一台机器上启动集群\n     *     但是同一台机器启动多个 broker 来实现集群就要使用不同的端口，因为《端口被占用，不能相同》\n     *     所以这里的配置是：\n     *     127.0.0.1:30056\n     *     127.0.0.1:30057\n     * </pre>\n     */\n    List<String> seedAddress;\n\n    /** Gossip listen port 监听端口 */\n    int gossipListenPort = IoGameGlobalConfig.gossipListenPort;\n\n    BrokerClusterManagerBuilder() {\n    }\n\n    public BrokerClusterManager build(BrokerServer brokerServer) {\n\n        this.checked();\n        // 种子节点\n        this.extractedSeedAddress();\n\n        // broker 端口（游戏网关端口）\n        int port = brokerServer.getPort();\n\n        BrokerClusterManager brokerClusterManager = new BrokerClusterManager();\n\n        ClusterMessageListenerImpl clusterMessageListener = new ClusterMessageListenerImpl();\n        clusterMessageListener.setBrokerServer(brokerServer);\n\n        brokerClusterManager\n                .setBrokerId(brokerServer.getBrokerId())\n                .setClusterMessageListener(clusterMessageListener)\n                // 种子节点地址\n                .setSeedAddress(seedAddress)\n                // Gossip listen port 监听端口\n                .setGossipListenPort(gossipListenPort)\n                // broker 端口（游戏网关端口）\n                .setPort(port);\n\n        return brokerClusterManager;\n    }\n\n    private void checked() {\n        if (this.gossipListenPort <= 0) {\n            ThrowKit.ofRuntimeException(\"gossipListenPort error!\");\n        }\n    }\n\n    private void extractedSeedAddress() {\n\n        if (Objects.isNull(this.seedAddress) || this.seedAddress.isEmpty()) {\n            this.seedAddress = List.of(\n                    \"127.0.0.1:30056\",\n                    \"127.0.0.1:30057\"\n            );\n\n            log.warn(\"因为你没有设置 种子节点信息，这里为你添加一些默认设置的种子节点\");\n        }\n\n        if (IoGameGlobalConfig.isBrokerClusterLog()) {\n            log.info(\"当前种子节点信息: {}\", this.seedAddress);\n        }\n    }\n\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/BrokerClusterMessageHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport com.iohao.game.bolt.broker.core.message.BrokerClusterMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport io.scalecube.cluster.Cluster;\nimport io.scalecube.cluster.ClusterMessageHandler;\nimport io.scalecube.cluster.Member;\nimport io.scalecube.cluster.membership.MembershipEvent;\nimport io.scalecube.cluster.transport.api.Message;\nimport io.scalecube.net.Address;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\nimport reactor.core.publisher.Sinks;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * ClusterMessageHandler impl\n *\n * @author 渔民小镇\n * @date 2023-05-27\n */\n@Slf4j(topic = IoGameLogName.ClusterTopic)\nfinal class BrokerClusterMessageHandler implements ClusterMessageHandler, Function<Cluster, ClusterMessageHandler> {\n    final String name;\n    final Broker localBroker;\n    final ClusterMessageListener clusterMessageListener;\n\n    /**\n     * broker map\n     * <pre>\n     *     key : address\n     *     value : broker\n     * </pre>\n     */\n    Map<String, Broker> brokers = new NonBlockingHashMap<>();\n    Cluster cluster;\n    /** brokers changes emitter processor */\n    Sinks.Many<Collection<Broker>> brokersEmitterProcessor = Sinks.many().multicast().onBackpressureBuffer();\n\n    public BrokerClusterMessageHandler(String name, Broker localBroker, ClusterMessageListener clusterMessageListener) {\n        this.name = name;\n        this.localBroker = localBroker;\n        this.clusterMessageListener = clusterMessageListener;\n    }\n\n    @Override\n    public ClusterMessageHandler apply(Cluster cluster) {\n        this.cluster = cluster;\n        return this;\n    }\n\n    @Override\n    public void onMessage(Message message) {\n        log.info(\"\\n{}\", name + \" received: \" + message.data());\n    }\n\n    @Override\n    public void onGossip(Message gossip) {\n        log.info(\"\\n{}\", name + \" received: \" + gossip.data());\n    }\n\n    @Override\n    public void onMembershipEvent(MembershipEvent event) {\n//        print(event);\n\n        Map<String, Broker> brokers = new NonBlockingHashMap<>();\n\n        this.cluster.members().forEach(member -> {\n            Optional<BrokerClusterMetadata> optional = cluster.metadata(member);\n            optional.ifPresent(metadata -> {\n                Broker memberBroker = metadata.getLocalBroker();\n\n                Address address = member.address();\n\n                Broker theBroker = new Broker(address.host())\n                        .setClusterAddress(address.toString())\n                        .setId(memberBroker.getId())\n                        .setPort(memberBroker.getPort())\n                        .setBrokerAddress(memberBroker.getBrokerAddress());\n\n                String clusterAddress = theBroker.getClusterAddress();\n                brokers.put(clusterAddress, theBroker);\n            });\n        });\n\n        // 使用新的 brokers。无论是 ADDED、REMOVED 都重新生成一次。\n        this.brokers = brokers;\n\n        this.brokersEmitterProcessor.tryEmitNext(this.brokers.values());\n\n        this.inform();\n    }\n\n    BrokerClusterMessage getBrokerClusterMessage() {\n        // 得到 Broker（游戏网关）列表\n        var brokerMessageList = this.brokers.values().stream().map(broker -> {\n            BrokerMessage item = new BrokerMessage();\n            item.setAddress(broker.getBrokerAddress());\n            item.setId(broker.getId());\n            return item;\n        }).collect(Collectors.toList());\n\n        // 集群消息\n        BrokerClusterMessage brokerClusterMessage = new BrokerClusterMessage();\n        brokerClusterMessage.setBrokerMessageList(brokerMessageList);\n        brokerClusterMessage.setName(this.localBroker.getBrokerAddress());\n\n        return brokerClusterMessage;\n    }\n\n    /**\n     * 发送集群信息给客户端（这里指的是逻辑服：对外服和游戏逻辑服）\n     */\n    private void inform() {\n        if (Objects.isNull(this.clusterMessageListener)) {\n            return;\n        }\n\n        BrokerClusterMessage brokerClusterMessage = getBrokerClusterMessage();\n        // 将集群信息发送给游戏对外服、游戏逻辑服\n        this.clusterMessageListener.inform(brokerClusterMessage);\n    }\n\n    private void print(MembershipEvent event) {\n\n        int size = cluster.members().size();\n        String mStr = cluster.members().stream()\n                .map(Member::toString)\n                .collect(Collectors.joining(\"\\n\"));\n\n        log.info(\"\\n{} {}\", name + \" received: \" + event.member().alias(), event);\n        log.info(\"size : {} {} \\n{}\", size, event.type(), mStr);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/BrokerClusterMetadata.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport lombok.AccessLevel;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serializable;\n\n/**\n * 集群 Metadata\n *\n * @author 渔民小镇\n * @date 2023-05-27\n */\n@Data\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class BrokerClusterMetadata implements Serializable {\n    /** memberAlias */\n    String name;\n    /** 对应的 broker */\n    Broker localBroker;\n\n    public BrokerClusterMetadata(String name, Broker localBroker) {\n        this.name = name;\n        this.localBroker = localBroker;\n    }\n\n    public BrokerClusterMetadata() {\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/BrokerRunModeEnum.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\n\n/**\n * broker （游戏网关）的启动模式\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\npublic enum BrokerRunModeEnum {\n    /** 单机启动模式 */\n    STANDALONE(Bundle.getMessage(MessageKey.gameBrokerServerStartupModeStandalone)),\n    /** 集群启动模式 */\n    CLUSTER(Bundle.getMessage(MessageKey.gameBrokerServerStartupModeCluster));\n\n    final String name;\n\n    BrokerRunModeEnum(String name) {\n        this.name = name;\n    }\n\n    @Override\n    public String toString() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/ClusterMessageListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.cluster;\n\nimport com.iohao.game.bolt.broker.core.message.BrokerClusterMessage;\n\n/**\n * 集群消息通知\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\npublic interface ClusterMessageListener {\n\n    /**\n     * 只要有变动，就通知逻辑服\n     * 对外服和游戏逻辑服两边都要通知到\n     *\n     * @param brokerClusterMessage 集群信息\n     */\n    void inform(BrokerClusterMessage brokerClusterMessage);\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/cluster/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 集群相关\n * <pre>\n *     目前只在 broker （游戏网关）做了集群，不打算把逻辑服也纳入这块的集群\n *     这样可以使得开发者可以简便的进行逻辑服的开发\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-12\n */\npackage com.iohao.game.bolt.broker.cluster;"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/BrokerServer.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server;\n\nimport com.alipay.remoting.rpc.RpcConfigs;\nimport com.alipay.remoting.rpc.RpcServer;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.cluster.BrokerClusterManager;\nimport com.iohao.game.bolt.broker.cluster.BrokerRunModeEnum;\nimport com.iohao.game.bolt.broker.core.GroupWith;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.service.BrokerClientModules;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport com.iohao.game.core.common.cmd.DefaultCmdRegions;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Optional;\n\n/**\n * Broker Server （游戏网关服）\n * <pre>\n *     通过 {@link BrokerServerBuilder#build()} 构建游戏网关服\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Getter\n@Accessors(chain = true)\n@Setter(AccessLevel.PACKAGE)\n@Slf4j(topic = IoGameLogName.CommonStdout)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerServer implements GroupWith {\n    final BalancedManager balancedManager = new BalancedManager(this);\n    final CmdRegions cmdRegions = new DefaultCmdRegions();\n\n\n    /**\n     * brokerId （游戏网关的id），服务器唯一标识\n     * <pre>\n     *     如果没设置，会随机分配一个\n     *\n     *     逻辑服的模块id，标记不同的逻辑服模块。\n     *     开发者随意定义，只要确保每个逻辑服的模块 id 不相同就可以\n     * </pre>\n     */\n    String brokerId;\n    /** broker 端口（游戏网关端口） */\n    int port;\n    /** rpc server */\n    RpcServer rpcServer;\n    /** broker （游戏网关）的启动模式，默认单机模式 */\n    BrokerRunModeEnum brokerRunMode;\n    /** 集群管理器 */\n    BrokerClusterManager brokerClusterManager;\n\n    BrokerClientModules brokerClientModules;\n\n    int withNo;\n\n    BrokerServer() {\n    }\n\n    void initRpcServer() {\n        this.rpcServer = new RpcServer(this.port, true);\n    }\n\n    public void startup() {\n        IoGameBanner.me().init();\n\n        // #100\n        System.setProperty(RpcConfigs.DISPATCH_MSG_LIST_IN_DEFAULT_EXECUTOR, \"false\");\n\n        // 启动 bolt rpc\n        this.rpcServer.startup();\n\n        // 启动集群\n        Optional.ofNullable(this.brokerClusterManager).ifPresent(BrokerClusterManager::start);\n\n        extractedLog();\n\n        IoGameBanner.render();\n        IoGameBanner.me().countDown();\n    }\n\n    private void extractedLog() {\n        String gameBrokerServer = Bundle.getMessage(MessageKey.gameBrokerServer);\n        String gameBrokerServerStartupMode = Bundle.getMessage(MessageKey.gameBrokerServerStartupMode);\n        log.info(\"{} port:[{}] - {}:[{}] \",\n                gameBrokerServer, this.port,\n                gameBrokerServerStartupMode, this.brokerRunMode\n        );\n    }\n\n    public void shutdown() {\n        this.rpcServer.shutdown();\n    }\n\n    public static BrokerServerBuilder newBuilder() {\n        return new BrokerServerBuilder();\n    }\n\n    @Override\n    public void setWithNo(int withNo) {\n        this.withNo = withNo;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/BrokerServerBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server;\n\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.alipay.remoting.config.Configs;\nimport com.alipay.remoting.rpc.RpcServer;\nimport com.alipay.remoting.rpc.protocol.UserProcessor;\nimport com.iohao.game.bolt.broker.cluster.BrokerClusterManager;\nimport com.iohao.game.bolt.broker.cluster.BrokerClusterManagerBuilder;\nimport com.iohao.game.bolt.broker.cluster.BrokerRunModeEnum;\nimport com.iohao.game.bolt.broker.core.aware.*;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.server.aware.BrokerClientModulesAware;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.LogicBrokerClientLoadBalanced;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegionFactory;\nimport com.iohao.game.bolt.broker.server.balanced.region.StrictBrokerClientRegion;\nimport com.iohao.game.bolt.broker.server.enhance.BrokerEnhances;\nimport com.iohao.game.bolt.broker.server.processor.*;\nimport com.iohao.game.bolt.broker.server.processor.ConnectionEventBrokerProcessor;\nimport com.iohao.game.bolt.broker.server.service.BrokerClientModules;\nimport com.iohao.game.bolt.broker.server.service.DefaultBrokerClientModules;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.*;\nimport java.util.concurrent.Executor;\nimport java.util.function.Supplier;\n\n/**\n * Broker Server （游戏网关服） 构建器\n * <pre>\n *     see {@link BrokerServer#newBuilder()}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@Accessors(fluent = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerServerBuilder implements AwareInject {\n    /** broker （游戏网关） */\n    final BrokerServer brokerServer = new BrokerServer();\n    /** 用户处理器 */\n    final List<Supplier<UserProcessor<?>>> processorList = new ArrayList<>();\n    /** bolt 连接器 */\n    final Map<ConnectionEventType, Supplier<ConnectionEventProcessor>> connectionEventProcessorMap = new NonBlockingHashMap<>();\n    final BrokerClientModules brokerClientModules = new DefaultBrokerClientModules();\n\n    /**\n     * brokerId （游戏网关的id），服务器唯一标识\n     * <pre>\n     *     如果没设置，会随机分配一个\n     *\n     *     逻辑服的模块id，标记不同的逻辑服模块。\n     *     开发者随意定义，只要确保每个逻辑服的模块 id 不相同就可以\n     * </pre>\n     */\n    @Setter\n    String brokerId;\n    /** broker 端口（游戏网关端口） */\n    @Setter\n    int port = IoGameGlobalConfig.brokerPort;\n    /** broker （游戏网关）的启动模式，默认单机模式 */\n    @Setter\n    BrokerRunModeEnum brokerRunMode = BrokerRunModeEnum.STANDALONE;\n    /** 集群的管理 构建器，如果不需要集群，可以不设置 */\n    BrokerClusterManagerBuilder brokerClusterManagerBuilder;\n\n    /** BrokerClientRegion 工厂 */\n    @Setter\n    BrokerClientRegionFactory brokerClientRegionFactory = StrictBrokerClientRegion::new;\n\n    BrokerServerBuilder() {\n        // 初始化一些处理器，如果开发者觉得默认的这些处理器没用，可以选择清除后，在添加自定义的。 this.clearProcessor\n        this.defaultProcessor();\n        // 开启 bolt 重连, 通过系统属性来开和关，如果一个进程有多个 RpcClient，则同时生效\n        System.setProperty(Configs.CONN_MONITOR_SWITCH, \"true\");\n        System.setProperty(Configs.CONN_RECONNECT_SWITCH, \"true\");\n    }\n\n    /**\n     * 构建游戏网关\n     *\n     * @return 游戏网关\n     */\n    public BrokerServer build() {\n\n        this.checked();\n\n        if (Objects.isNull(this.brokerId)) {\n            this.brokerId = UUID.randomUUID().toString();\n        }\n\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        LogicBrokerClientLoadBalanced logicBalanced = balancedManager.getLogicBalanced();\n        logicBalanced.setBrokerClientRegionFactory(this.brokerClientRegionFactory);\n\n        brokerServer\n                .setBrokerId(this.brokerId)\n                .setBrokerRunMode(this.brokerRunMode)\n                .setPort(this.port)\n                .setBrokerClientModules(this.brokerClientModules)\n        ;\n\n        // 初始化 boltRpcServer\n        brokerServer.initRpcServer();\n\n        RpcServer rpcServer = brokerServer.getRpcServer();\n\n        // 注册用户处理器 添加到 bolt rpcServer 中\n        this.processorList.forEach(processorSupplier -> {\n\n            UserProcessor<?> userProcessor = processorSupplier.get();\n\n            aware(userProcessor);\n\n            rpcServer.registerUserProcessor(userProcessor);\n        });\n\n        // 注册连接器 添加到 bolt rpcServer 中\n        connectionEventProcessorMap.forEach((type, valueSupplier) -> {\n\n            var processor = valueSupplier.get();\n\n            aware(processor);\n\n            rpcServer.addConnectionEventProcessor(type, processor);\n        });\n\n        // 集群相关\n        this.cluster();\n\n        return brokerServer;\n    }\n\n    /**\n     * 注册用户处理器\n     *\n     * @param processorSupplier processor\n     * @return this\n     */\n    public BrokerServerBuilder registerUserProcessor(Supplier<UserProcessor<?>> processorSupplier) {\n        this.processorList.add(processorSupplier);\n        return this;\n    }\n\n    /**\n     * 移除用户处理器，使之不与自定义的用户处理器冲突\n     *\n     * @param clazz 待移除的用户处理器类型\n     * @return this\n     */\n    public BrokerServerBuilder removeUserProcessor(Class<? extends UserProcessor<?>> clazz) {\n\n        if (clazz != null) {\n            this.processorList.removeIf(processorSupplier -> processorSupplier.get().getClass().equals(clazz));\n        }\n\n        return this;\n    }\n\n    /**\n     * 注册连接器\n     *\n     * @param type              type\n     * @param processorSupplier processorSupplier\n     * @return this\n     */\n    public BrokerServerBuilder addConnectionEventProcessor(ConnectionEventType type, Supplier<ConnectionEventProcessor> processorSupplier) {\n        this.connectionEventProcessorMap.put(type, processorSupplier);\n        return this;\n    }\n\n    /**\n     * 集群构建器\n     * <pre>\n     *     如果不设置，表示不需要集群\n     * </pre>\n     *\n     * @param brokerClusterManagerBuilder brokerClusterManagerBuilder\n     * @return this\n     */\n    public BrokerServerBuilder brokerClusterManagerBuilder(BrokerClusterManagerBuilder brokerClusterManagerBuilder) {\n\n        if (Objects.isNull(brokerClusterManagerBuilder)) {\n            return this;\n        }\n\n        this.brokerClusterManagerBuilder = brokerClusterManagerBuilder;\n        // 表示集群方式启动 broker （游戏网关）\n        this.brokerRunMode = BrokerRunModeEnum.CLUSTER;\n\n        return this;\n    }\n\n    /**\n     * 移除所有默认 处理器\n     * <pre>\n     *     如果框架满足不了你的业务，你可以把框架默认的处理器移除，这样就可以完全的重新定义\n     * </pre>\n     *\n     * @return this\n     */\n    public BrokerServerBuilder clearProcessor() {\n        this.processorList.clear();\n        this.connectionEventProcessorMap.clear();\n        return this;\n    }\n\n    private void cluster() {\n        // 单机模式，不做处理\n        if (this.brokerRunMode != BrokerRunModeEnum.CLUSTER) {\n            return;\n        }\n\n        Objects.requireNonNull(this.brokerClusterManagerBuilder, \"开启集群模式 brokerClusterManagerBuilder 必须不为 null!\");\n\n        // ==========到这里表示是集群模式==========\n        BrokerClusterManager brokerClusterManager = this.brokerClusterManagerBuilder.build(this.brokerServer);\n\n        // 设置集群管理器\n        this.brokerServer.setBrokerClusterManager(brokerClusterManager);\n    }\n\n    private void checked() {\n        if (this.port <= 0) {\n            ThrowKit.ofRuntimeException(\"port error!\");\n        }\n\n        if (Objects.isNull(this.brokerRunMode)) {\n            ThrowKit.ofRuntimeException(\"brokerRunMode expected: \" + Arrays.toString(BrokerRunModeEnum.values()));\n        }\n    }\n\n    private void defaultProcessor() {\n        // ============================注册连接器============================\n\n        Supplier<ConnectionEventProcessor> connectionCloseEventSupplier = ConnectionCloseEventBrokerProcessor::new;\n        Supplier<ConnectionEventProcessor> connectionEventSupplier = ConnectionEventBrokerProcessor::new;\n        Supplier<ConnectionEventProcessor> connectionExceptionEventSupplier = ConnectionExceptionEventBrokerProcessor::new;\n        Supplier<ConnectionEventProcessor> connectionFailedEventSupplier = ConnectionFailedEventBrokerProcessor::new;\n\n        this\n                .addConnectionEventProcessor(ConnectionEventType.EXCEPTION, connectionExceptionEventSupplier)\n                .addConnectionEventProcessor(ConnectionEventType.CONNECT_FAILED, connectionFailedEventSupplier)\n                .addConnectionEventProcessor(ConnectionEventType.CONNECT, connectionEventSupplier)\n                .addConnectionEventProcessor(ConnectionEventType.CLOSE, connectionCloseEventSupplier);\n\n        // ============================注册用户处理器============================\n\n        // 处理 - 模块注册（逻辑服注册）\n        Supplier<UserProcessor<?>> registerSupplier = RegisterBrokerClientModuleMessageBrokerProcessor::new;\n\n        // 处理 - (接收真实用户的请求) 把对外服的请求转发到逻辑服\n        Supplier<UserProcessor<?>> externalMessageSupplier = RequestMessageBrokerProcessor::new;\n\n        // 处理 - 改变用户 id -- external server\n        Supplier<UserProcessor<?>> changeUserIdMessageSupplier = SettingUserIdMessageBrokerProcessor::new;\n\n        // 处理 - （响应真实用户的请求）把逻辑服的响应转发到对外服\n        Supplier<UserProcessor<?>> responseMessageSupplier = ResponseMessageBrokerProcessor::new;\n\n        // 处理 - 内部模块消息的转发\n        Supplier<UserProcessor<?>> innerModuleMessageSupplier = InnerModuleMessageBrokerProcessor::new;\n        // 处理 - 内部模块消息的转发\n        Supplier<UserProcessor<?>> innerModuleVoidMessageSupplier = InnerModuleVoidMessageBrokerProcessor::new;\n\n        // 处理 - 模块之间的访问，访问同类型的多个逻辑服\n        Supplier<UserProcessor<?>> innerModuleRequestCollectMessageSupplier = InnerModuleRequestCollectMessageBrokerProcessor::new;\n        // 处理 - 模块之间的访问，游戏逻辑服同时访问多个游戏对外服\n        Supplier<UserProcessor<?>> innerModuleRequestCollectExternalMessageSupplier = InnerModuleRequestCollectExternalMessageBrokerProcessor::new;\n\n        // 处理 - 把绑定消息转发到对外服\n        Supplier<UserProcessor<?>> endPointLogicServerMessageSupplier = EndPointLogicServerMessageBrokerProcessor::new;\n\n        // 处理 - 广播\n        Supplier<UserProcessor<?>> broadcastMessageSupplier = BroadcastMessageBrokerProcessor::new;\n        // 处理 - 顺序的广播\n        Supplier<UserProcessor<?>> broadcastOrderMessageSupplier = BroadcastOrderMessageBrokerProcessor::new;\n\n        Supplier<UserProcessor<?>> brokerClientItemConnectMessageSupplier = BrokerClientItemConnectMessageBrokerProcessor::new;\n\n        this\n                .registerUserProcessor(registerSupplier)\n                .registerUserProcessor(externalMessageSupplier)\n                .registerUserProcessor(changeUserIdMessageSupplier)\n                .registerUserProcessor(responseMessageSupplier)\n                .registerUserProcessor(innerModuleMessageSupplier)\n                .registerUserProcessor(innerModuleVoidMessageSupplier)\n                .registerUserProcessor(innerModuleRequestCollectMessageSupplier)\n                .registerUserProcessor(innerModuleRequestCollectExternalMessageSupplier)\n                .registerUserProcessor(broadcastMessageSupplier)\n                .registerUserProcessor(broadcastOrderMessageSupplier)\n                .registerUserProcessor(brokerClientItemConnectMessageSupplier)\n                .registerUserProcessor(endPointLogicServerMessageSupplier)\n                // 处理 - 接收脉冲生产者-的脉冲信号\n                .registerUserProcessor(PulseSignalRequestBrokerProcessor::new)\n                // 处理 - 接收脉冲消费者-的脉冲信号\n                .registerUserProcessor(PulseSignalResponseBrokerProcessor::new)\n                // 分布式事件总线 broker\n                .registerUserProcessor(EventBusMessageBrokerProcessor::new)\n        ;\n\n        BrokerEnhances.enhance(this);\n    }\n\n    @Override\n    public void aware(Object obj) {\n        /*\n         * 目前 aware 系列由框架提供，\n         * 虽然这里可以开放给开发者来控制，但目前暂时不考虑开放\n         */\n\n        AwareKit.aware(obj);\n\n        if (obj instanceof BrokerServerAware aware) {\n            aware.setBrokerServer(this.brokerServer);\n        }\n\n        if (obj instanceof CmdRegionsAware aware) {\n            aware.setCmdRegions(this.brokerServer.getCmdRegions());\n        }\n\n        if (obj instanceof BrokerClientModulesAware aware) {\n            aware.setBrokerClientModules(this.brokerClientModules);\n        }\n    }\n}"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/aware/BrokerClientModulesAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.aware;\n\nimport com.iohao.game.bolt.broker.server.service.BrokerClientModules;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-01\n */\npublic interface BrokerClientModulesAware {\n    void setBrokerClientModules(BrokerClientModules brokerClientModules);\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/aware/BrokerServerAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.aware;\n\nimport com.iohao.game.bolt.broker.server.BrokerServer;\n\n/**\n * BrokerServerAware\n * <pre>\n *     设置与 broker（游戏网关）server\n *\n *     只要 bolt 处理器（逻辑处理器和连接器）实现了该接口，框架会调用 setBrokerServer 方法并赋值\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\npublic interface BrokerServerAware {\n    /**\n     * set BrokerServer\n     *\n     * @param brokerServer brokerServer\n     */\n    void setBrokerServer(BrokerServer brokerServer);\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/BalancedManager.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced;\n\nimport com.alipay.remoting.rpc.RpcServer;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientType;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegion;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.*;\n\n/**\n * 负载管理器\n * <pre>\n *     对外服和游戏逻辑服的负载相关管理\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-12\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BalancedManager {\n    /** 逻辑服 负载均衡 */\n    @Getter\n    final LogicBrokerClientLoadBalanced logicBalanced = new LogicBrokerClientLoadBalanced();\n    /** 对外服 负载均衡 */\n    @Getter\n    final ExternalBrokerClientLoadBalanced externalLoadBalanced = new ExternalBrokerClientLoadBalanced();\n    /** key:address value:proxy */\n    final Map<String, BrokerClientProxy> refMap = new NonBlockingHashMap<>();\n\n    final BrokerServer brokerServer;\n\n    public BalancedManager(BrokerServer brokerServer) {\n        this.brokerServer = brokerServer;\n    }\n\n    /**\n     * 根据 brokerClient 类型，得到对应的负载器\n     *\n     * @param brokerClientType brokerClientType\n     * @return 负载器\n     */\n    public BrokerClientLoadBalanced getRegionLoadBalanced(BrokerClientType brokerClientType) {\n        if (brokerClientType == BrokerClientType.EXTERNAL) {\n            return this.externalLoadBalanced;\n        }\n\n        return this.logicBalanced;\n    }\n\n    public void register(BrokerClientModuleMessage brokerClientModuleMessage) {\n\n        // 得到游戏逻辑服或对外服的负载器\n        var brokerClientType = brokerClientModuleMessage.getBrokerClientType();\n        var loadBalanced = this.getRegionLoadBalanced(brokerClientType);\n\n        String address = brokerClientModuleMessage.getAddress();\n\n        // broker client\n        RpcServer rpcServer = this.brokerServer.getRpcServer();\n        BrokerClientProxy brokerClientProxy = new BrokerClientProxy(brokerClientModuleMessage, rpcServer);\n\n        loadBalanced.register(brokerClientProxy);\n\n        brokerClientProxy.setCmdMergeList(null);\n\n        this.refMap.put(address, brokerClientProxy);\n    }\n\n    public BrokerClientProxy remove(String address) {\n        BrokerClientProxy brokerClientProxy = this.refMap.get(address);\n\n        if (Objects.isNull(brokerClientProxy)) {\n            return null;\n        }\n\n        BrokerClientType brokerClientType = brokerClientProxy.getBrokerClientType();\n        var loadBalanced = this.getRegionLoadBalanced(brokerClientType);\n\n        // 根据 address 来移除逻辑服（对外服或游戏逻辑服）\n        loadBalanced.remove(brokerClientProxy);\n\n        return brokerClientProxy;\n    }\n\n    /**\n     * 得到游戏逻辑服和游戏对外服的列表\n     *\n     * @return client list\n     */\n    public List<BrokerClientProxy> listBrokerClientProxy() {\n\n        // 当前网关的所有逻辑服\n        List<BrokerClientProxy> list = new ArrayList<>(16);\n\n        // 游戏对外服\n        var externalProxyList = this.externalLoadBalanced.listBrokerClientProxy();\n        list.addAll(externalProxyList);\n\n        // 游戏逻辑服\n        Collection<BrokerClientRegion> brokerClientRegions = this.logicBalanced.listBrokerClientRegion();\n        for (BrokerClientRegion brokerClientRegion : brokerClientRegions) {\n            var logicProxyList = brokerClientRegion.listBrokerClientProxy();\n            list.addAll(logicProxyList);\n        }\n\n        return list;\n    }\n\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/BrokerClientLoadBalanced.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced;\n\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\n\n/**\n * 逻辑服管理器\n * <pre>\n *     注意：\n *     对外服中与 broker 建立连接的的 boltBrokerClient ，所以对外服也属于是逻辑服的一种\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\npublic interface BrokerClientLoadBalanced {\n    /**\n     * 注册逻辑服\n     *\n     * @param brokerClientProxy brokerClientProxy\n     */\n    void register(BrokerClientProxy brokerClientProxy);\n\n    /**\n     * 删除逻辑服\n     *\n     * @param brokerClientProxy brokerClientProxy\n     */\n    void remove(BrokerClientProxy brokerClientProxy);\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/ExternalBrokerClientLoadBalanced.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced;\n\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * 对外服的管理器\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ExternalBrokerClientLoadBalanced implements BrokerClientLoadBalanced {\n    /**\n     * 对外服的关联 map\n     * <pre>\n     *     key : external id\n     *     value : external info\n     * </pre>\n     */\n    final Map<Integer, BrokerClientProxy> map = new NonBlockingHashMap<>();\n    /** 对外服 list */\n    List<BrokerClientProxy> list = Collections.emptyList();\n\n    @Override\n    public void register(BrokerClientProxy brokerClientProxy) {\n\n        int externalId = brokerClientProxy.getIdHash();\n        this.map.put(externalId, brokerClientProxy);\n\n        this.resetSelector();\n    }\n\n    @Override\n    public void remove(BrokerClientProxy brokerClientProxy) {\n\n        int externalId = brokerClientProxy.getIdHash();\n\n        this.map.remove(externalId);\n\n        this.resetSelector();\n    }\n\n    public BrokerClientProxy get(int externalId) {\n        return this.map.get(externalId);\n    }\n\n    public boolean contains(int externalId) {\n        return this.map.containsKey(externalId);\n    }\n\n    /**\n     * 所有的对外服\n     *\n     * @return 对外服列表\n     */\n    public List<BrokerClientProxy> listBrokerClientProxy() {\n        return this.list;\n    }\n\n    public int count() {\n        return this.map.size();\n    }\n\n    private void resetSelector() {\n        this.list = new CopyOnWriteArrayList<>(map.values());\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/LogicBrokerClientLoadBalanced.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced;\n\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegion;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegionFactory;\nimport com.iohao.game.common.kit.MoreKit;\nimport lombok.Setter;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 逻辑服域的负载均衡\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\npublic final class LogicBrokerClientLoadBalanced implements BrokerClientLoadBalanced {\n\n    /**\n     * 路由与逻辑服域的关联\n     * <pre>\n     *     key : cmdMerge\n     *     value : BrokerClientRegion\n     * </pre>\n     */\n    final Map<Integer, BrokerClientRegion> cmdClientRegionMap = new NonBlockingHashMap<>();\n\n    /**\n     * 逻辑服tag 与逻辑服域的关联\n     * <pre>\n     *     key : tag\n     *     value : BrokerClientRegion\n     * </pre>\n     */\n    final Map<String, BrokerClientRegion> tagClientRegionMap = new NonBlockingHashMap<>();\n\n    final Map<Integer, BrokerClientProxy> serverIdClientProxyMap = new NonBlockingHashMap<>();\n\n    @Setter\n    BrokerClientRegionFactory brokerClientRegionFactory;\n\n    @Override\n    public void register(BrokerClientProxy brokerClientProxy) {\n\n        // 相同业务模块（逻辑服）的信息域\n        String tag = brokerClientProxy.getTag();\n\n        BrokerClientRegion brokerClientRegion = getBrokerClientRegionByTag(tag);\n        brokerClientRegion.add(brokerClientProxy);\n\n        // 路由与逻辑服域的关联\n        var cmdMergeList = brokerClientProxy.getCmdMergeList();\n        for (Integer cmdMerge : cmdMergeList) {\n            this.cmdClientRegionMap.put(cmdMerge, brokerClientRegion);\n        }\n\n        this.serverIdClientProxyMap.put(brokerClientProxy.getIdHash(), brokerClientProxy);\n    }\n\n    @Override\n    public void remove(BrokerClientProxy brokerClientProxy) {\n\n        int id = brokerClientProxy.getIdHash();\n\n        // 相同业务模块（逻辑服）的信息域\n        String tag = brokerClientProxy.getTag();\n        BrokerClientRegion brokerClientRegion = getBrokerClientRegionByTag(tag);\n        brokerClientRegion.remove(id);\n\n        this.serverIdClientProxyMap.remove(brokerClientProxy.getIdHash());\n    }\n\n    /**\n     * get BrokerClientRegion\n     *\n     * @param cmdMerge cmdMerge\n     * @return BrokerClientRegion\n     */\n    public BrokerClientRegion getBrokerClientRegion(int cmdMerge) {\n        // 通过 路由信息 得到对应的逻辑服列表（域）\n        BrokerClientRegion region = this.cmdClientRegionMap.get(cmdMerge);\n\n        if (Objects.isNull(region)) {\n            return null;\n        }\n\n        return region;\n    }\n\n    public Collection<BrokerClientRegion> listBrokerClientRegion() {\n        return this.tagClientRegionMap.values();\n    }\n\n    public BrokerClientProxy getBrokerClientProxyByIdHash(int idHash) {\n        return this.serverIdClientProxyMap.get(idHash);\n    }\n\n    private BrokerClientRegion getBrokerClientRegionByTag(String tag) {\n        BrokerClientRegion brokerClientRegion = this.tagClientRegionMap.get(tag);\n\n        // 无锁化\n        if (Objects.isNull(brokerClientRegion)) {\n            BrokerClientRegion newValue = this.brokerClientRegionFactory.createBrokerClientRegion(tag);\n            return MoreKit.putIfAbsent(tagClientRegionMap, tag, newValue);\n        }\n\n        return brokerClientRegion;\n    }\n\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/region/BrokerClientProxy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced.region;\n\nimport com.alipay.remoting.exception.RemotingException;\nimport com.alipay.remoting.rpc.RpcServer;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientType;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\n\nimport java.util.List;\n\n/**\n * 模块信息代理\n * <pre>\n *     这里的模块指的是逻辑服信息\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Getter\n@Setter\n@ToString\n@SuppressWarnings(\"unchecked\")\npublic class BrokerClientProxy {\n    /** 逻辑服唯一标识 */\n    final String id;\n    final int idHash;\n    /** 逻辑服名 */\n    final String name;\n    /** 逻辑服地址 */\n    final String address;\n    /**\n     * 逻辑服标签 （tag 相当于归类）\n     * <pre>\n     *     用于逻辑服的归类\n     *     假设逻辑服： 战斗逻辑服 启动了两台或以上，为了得到启动连接的逻辑服，我们可以通过 tag 在后台查找\n     *     相同的逻辑服一定要用相同的 tag\n     *\n     *     注意，如果没设置这个值，会使用 this.name 的值\n     * </pre>\n     */\n    final String tag;\n\n    /** 逻辑服类型 */\n    final BrokerClientType brokerClientType;\n    final RpcServer rpcServer;\n    final int withNo;\n\n    /** 状态 */\n    int status;\n\n    /** 消息发送超时时间 */\n    int timeoutMillis = IoGameGlobalConfig.timeoutMillis;\n    List<Integer> cmdMergeList;\n\n    public BrokerClientProxy(BrokerClientModuleMessage brokerClientModuleMessage, RpcServer rpcServer) {\n        this.id = brokerClientModuleMessage.getId();\n        this.idHash = brokerClientModuleMessage.getIdHash();\n        this.name = brokerClientModuleMessage.getName();\n        this.address = brokerClientModuleMessage.getAddress();\n        this.tag = brokerClientModuleMessage.getTag();\n        this.brokerClientType = brokerClientModuleMessage.getBrokerClientType();\n        this.cmdMergeList = brokerClientModuleMessage.getCmdMergeList();\n        this.rpcServer = rpcServer;\n        this.withNo = brokerClientModuleMessage.getWithNo();\n        this.status = brokerClientModuleMessage.getStatus();\n    }\n\n    public void oneway(Object request) throws RemotingException, InterruptedException {\n        rpcServer.oneway(address, request);\n    }\n\n    public <T> T invokeSync(Object message) throws RemotingException, InterruptedException {\n        return (T) rpcServer.invokeSync(address, message, timeoutMillis);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof BrokerClientProxy that)) {\n            return false;\n        }\n\n        return id.equals(that.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return id.hashCode();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/region/BrokerClientRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced.region;\n\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\n\nimport java.util.Collection;\nimport java.util.Map;\n\n/**\n * 负载均衡，相同业务模块（逻辑服）的信息域\n * <pre>\n *     即同一个业务模块起了N个服务（来负载）\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\npublic interface BrokerClientRegion {\n    /**\n     * tag\n     *\n     * @return tag\n     */\n    String getTag();\n\n    /**\n     * 当前逻辑服域有多少个逻辑服\n     *\n     * @return 统计\n     */\n    default int count() {\n        return this.getBrokerClientProxyMap().size();\n    }\n\n    /**\n     * 添加逻辑服\n     *\n     * @param brokerClientProxy 逻辑服信息\n     */\n    void add(BrokerClientProxy brokerClientProxy);\n\n    /**\n     * 根据请求元信息得到一个 逻辑服\n     *\n     * @param headMetadata 请求元信息\n     * @return 逻辑服信息\n     */\n    BrokerClientProxy getBrokerClientProxy(HeadMetadata headMetadata);\n\n    /**\n     * BrokerClientProxy map\n     *\n     * @return map\n     */\n    Map<Integer, BrokerClientProxy> getBrokerClientProxyMap();\n\n    /**\n     * 域下的所有 逻辑服\n     *\n     * @return 逻辑服列表\n     */\n    default Collection<BrokerClientProxy> listBrokerClientProxy() {\n        /*\n         * NonBlockingHashMap 迭代器是一个“弱一致性”迭代器，\n         * 它永远不会抛出 ConcurrentModificationException\n         */\n        return getBrokerClientProxyMap().values();\n    }\n\n    /**\n     * 根据 id 删除逻辑服\n     *\n     * @param id id\n     */\n    void remove(int id);\n\n    /**\n     * doAnything\n     *\n     * @param value value\n     */\n    default void doAnything(Object value) {\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/region/BrokerClientRegionFactory.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced.region;\n\n/**\n * BrokerClientRegion 工厂\n * <pre>\n *     生产游戏逻辑服的 BrokerClientRegion 域的工厂\n *\n *     开放这个接口的原因，可以使得开发者可以实现自己的负载算法\n *\n *     框架默认的实现是随机负载 see {@link DefaultBrokerClientRegion}\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\npublic interface BrokerClientRegionFactory {\n    /**\n     * 创建游戏逻辑服的 BrokerClientRegion 域\n     *\n     * @param tag 逻辑服 tag\n     * @return BrokerClientRegion\n     */\n    BrokerClientRegion createBrokerClientRegion(String tag);\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/region/DefaultBrokerClientRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced.region;\n\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.bolt.broker.core.loadbalance.ElementSelector;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 负载均衡，相同业务模块（逻辑服）的信息域\n * <pre>\n *     即同一个业务模块起了N个服务（来负载）\n *\n *     如果绑定的游戏逻辑服的，在没有找到时会取一个其他的游戏逻辑服来处理。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\n@FieldDefaults(level = AccessLevel.PROTECTED)\npublic class DefaultBrokerClientRegion implements BrokerClientRegion {\n    /**\n     * 模块信息代理，这里的模块指的是逻辑服信息\n     * <pre>\n     *     key : 逻辑服 id\n     *     value : 逻辑服代理\n     * </pre>\n     */\n    @Getter\n    final Map<Integer, BrokerClientProxy> brokerClientProxyMap = new NonBlockingHashMap<>();\n    final String tag;\n\n    ElementSelector<BrokerClientProxy> elementSelector;\n\n    public DefaultBrokerClientRegion(String tag) {\n        this.tag = tag;\n    }\n\n    @Override\n    public BrokerClientProxy getBrokerClientProxy(HeadMetadata headMetadata) {\n        int endPointClientId = headMetadata.getEndPointClientId();\n\n        // 得到指定的逻辑服\n        if (endPointClientId != 0 && this.brokerClientProxyMap.containsKey(endPointClientId)) {\n\n            /*\n             * 查看当前 endPointClientId 是否属于当前 Region\n             *\n             * 理论上需要保存一下所有\"注册过\"的游戏逻辑服id，\n             * 因为动态绑定逻辑服时，玩家绑定的逻辑服有可能关闭了或下线了，\n             * 这种情况应该返回 null ，这样可以通知对外服， 路由不存在，\n             * 但目前先不做这样的判断。\n             */\n\n            // 如果找到了就返回，没找到则使用继续往下找\n            BrokerClientProxy brokerClientProxy = this.brokerClientProxyMap.get(endPointClientId);\n            if (Objects.nonNull(brokerClientProxy)) {\n                return brokerClientProxy;\n            }\n        }\n\n        if (Objects.isNull(this.elementSelector)) {\n            return null;\n        }\n\n        // 随机选一个逻辑服\n        return this.elementSelector.get();\n    }\n\n    @Override\n    public void add(BrokerClientProxy brokerClientProxy) {\n        int id = brokerClientProxy.getIdHash();\n        this.brokerClientProxyMap.put(id, brokerClientProxy);\n        this.resetSelector();\n    }\n\n    @Override\n    public void remove(int id) {\n        this.brokerClientProxyMap.remove(id);\n        this.resetSelector();\n    }\n\n    @Override\n    public String getTag() {\n        return this.tag;\n    }\n\n    @Override\n    public int count() {\n        return this.brokerClientProxyMap.size();\n    }\n\n    private void resetSelector() {\n        // 随机选择器\n        List<BrokerClientProxy> list = new ArrayList<>(brokerClientProxyMap.values());\n        this.elementSelector = ElementSelector.of(list);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/region/DefaultWithElementSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced.region;\n\nimport com.iohao.game.bolt.broker.core.loadbalance.ElementSelector;\nimport com.iohao.game.common.kit.CollKit;\n\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-10\n */\nfinal class DefaultWithElementSelector implements WithElementSelector<BrokerClientProxy> {\n\n    int size;\n\n    /**\n     * <pre>\n     *     key : withNo\n     *     value : 逻辑服代理\n     * </pre>\n     */\n    Map<Integer, List<BrokerClientProxy>> map;\n    ElementSelector<BrokerClientProxy> elementSelector;\n    final AtomicLong counter = new AtomicLong();\n\n    public DefaultWithElementSelector(Map<Integer, BrokerClientProxy> proxyMap) {\n        List<BrokerClientProxy> list = proxyMap.values().stream().filter(Objects::nonNull).toList();\n        if ((size = list.size()) == 0) {\n            return;\n        }\n\n        elementSelector = ElementSelector.of(list);\n        this.map = new HashMap<>();\n\n        for (BrokerClientProxy brokerClientProxy : list) {\n            int withNo = brokerClientProxy.getWithNo();\n\n            if (withNo == 0) {\n                continue;\n            }\n\n            List<BrokerClientProxy> withList = this.map.get(withNo);\n            if (Objects.isNull(withList)) {\n                withList = new ArrayList<>();\n                this.map.put(withNo, withList);\n            }\n\n            withList.add(brokerClientProxy);\n        }\n    }\n\n    @Override\n    public BrokerClientProxy next(int withNo) {\n\n        if (size == 0) {\n            return null;\n        }\n\n        if (withNo == 0) {\n            // 随机选一个逻辑服\n            return this.elementSelector.get();\n        }\n\n        var withList = this.map.get(withNo);\n        if (CollKit.notEmpty(withList)) {\n            var brokerClientProxy = withList.get((int) (counter.getAndIncrement() % withList.size()));\n            if (Objects.nonNull(brokerClientProxy)) {\n                return brokerClientProxy;\n            }\n        }\n\n        // 随机选一个逻辑服\n        return this.elementSelector.next();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/region/StrictBrokerClientRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced.region;\n\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 负载均衡，相同业务模块（逻辑服）的信息域\n * <pre>\n *     即同一个业务模块起了N个服务（来负载）\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-06-06\n */\npublic final class StrictBrokerClientRegion implements BrokerClientRegion {\n    /**\n     * 模块信息代理，这里的模块指的是逻辑服信息\n     * <pre>\n     *     key : 逻辑服 id\n     *     value : 逻辑服代理\n     * </pre>\n     */\n    final Map<Integer, BrokerClientProxy> brokerClientProxyMap = new NonBlockingHashMap<>();\n    final String tag;\n    WithElementSelector<BrokerClientProxy> withElementSelector;\n\n    public StrictBrokerClientRegion(String tag) {\n        this.tag = tag;\n    }\n\n    @Override\n    public BrokerClientProxy getBrokerClientProxy(HeadMetadata headMetadata) {\n        // 绑定的逻辑服 id\n        int endPointClientId = headMetadata.getEndPointClientId();\n        if (endPointClientId != 0) {\n            // 通过绑定的逻辑服 id 来查找对应的游戏逻辑服\n            BrokerClientProxy brokerClientProxy = this.brokerClientProxyMap.get(endPointClientId);\n\n            if (Objects.isNull(brokerClientProxy)) {\n                headMetadata.setOther(ActionErrorEnum.findBindingLogicServerNotExist);\n            }\n\n            return brokerClientProxy;\n        }\n\n        if (Objects.isNull(this.withElementSelector)) {\n            return null;\n        }\n\n        return this.withElementSelector.next(headMetadata.getWithNo());\n    }\n\n    @Override\n    public void add(BrokerClientProxy brokerClientProxy) {\n        int id = brokerClientProxy.getIdHash();\n        this.brokerClientProxyMap.put(id, brokerClientProxy);\n        this.resetSelector();\n    }\n\n    @Override\n    public void remove(int id) {\n        this.brokerClientProxyMap.remove(id);\n        this.resetSelector();\n    }\n\n    @Override\n    public String getTag() {\n        return this.tag;\n    }\n\n    @Override\n    public int count() {\n        return this.brokerClientProxyMap.size();\n    }\n\n    @Override\n    public Map<Integer, BrokerClientProxy> getBrokerClientProxyMap() {\n        return this.brokerClientProxyMap;\n    }\n\n    private void resetSelector() {\n        this.withElementSelector = new DefaultWithElementSelector(this.brokerClientProxyMap);\n    }\n}"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/region/WithElementSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.balanced.region;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-18\n */\npublic interface WithElementSelector<T> {\n    T next(int withNo);\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/cluster/ClusterMessageListenerImpl.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.cluster;\n\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.bolt.broker.cluster.ClusterMessageListener;\nimport com.iohao.game.bolt.broker.core.message.BrokerClusterMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegion;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\n/**\n * 集群消息通知\n * <pre>\n *     发送集群信息给客户端（这里指的是逻辑服：对外服和游戏逻辑服）\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@Setter\n@Slf4j(topic = IoGameLogName.ClusterTopic)\npublic class ClusterMessageListenerImpl implements ClusterMessageListener {\n    BrokerServer brokerServer;\n\n    @Override\n    public void inform(BrokerClusterMessage brokerClusterMessage) {\n        // 把集群消息发送给所有 游戏逻辑服\n        extractedLogic(brokerClusterMessage);\n\n        // 把集群消息发送给所有 游戏对外服\n        extractedExternal(brokerClusterMessage);\n    }\n\n    private void extractedLogic(BrokerClusterMessage brokerClusterMessage) {\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        // 得到逻辑服负载器\n        balancedManager\n                .getLogicBalanced()\n                // 得到逻辑服域 BrokerClientRegion\n                .listBrokerClientRegion()\n                .stream()\n                // 得到 BrokerClientRegion 下的所有逻辑服\n                .flatMap((Function<BrokerClientRegion, Stream<BrokerClientProxy>>) brokerClientRegion -> brokerClientRegion.listBrokerClientProxy().stream())\n                // 给游戏逻辑服发送集群消息\n                .forEach(brokerClientProxy -> {\n                    try {\n                        brokerClientProxy.oneway(brokerClusterMessage);\n                    } catch (RemotingException | InterruptedException e) {\n                        log.error(e.getMessage(), e);\n                    }\n                });\n    }\n\n    private void extractedExternal(BrokerClusterMessage brokerClusterMessage) {\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        // 对外服列表\n        balancedManager\n                .getExternalLoadBalanced()\n                .listBrokerClientProxy()\n                .forEach(brokerClientProxy -> {\n                    try {\n                        brokerClientProxy.oneway(brokerClusterMessage);\n                    } catch (RemotingException | InterruptedException e) {\n                        log.error(e.getMessage(), e);\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/enhance/BrokerEnhance.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.enhance;\n\nimport com.iohao.game.bolt.broker.server.BrokerServerBuilder;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-16\n */\npublic interface BrokerEnhance {\n    void enhance(BrokerServerBuilder builder);\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/enhance/BrokerEnhances.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.enhance;\n\nimport com.iohao.game.bolt.broker.server.BrokerServerBuilder;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.ServiceLoader;\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-16\n */\n@Slf4j\n@UtilityClass\npublic class BrokerEnhances {\n    final Set<BrokerEnhance> enhanceSet = new NonBlockingHashSet<>();\n\n    static {\n        ServiceLoader.load(BrokerEnhance.class).forEach(BrokerEnhances::add);\n    }\n\n    void add(BrokerEnhance enhance) {\n        enhanceSet.add(enhance);\n    }\n\n    public void enhance(BrokerServerBuilder builder) {\n        for (BrokerEnhance enhance : enhanceSet) {\n            enhance.enhance(builder);\n        }\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/kit/BrokerPrintKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.kit;\n\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.stream.Collectors;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-12\n */\n@UtilityClass\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class BrokerPrintKit {\n    public void print(BrokerServer brokerServer) {\n        if (!IoGameGlobalConfig.openLog) {\n            return;\n        }\n\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n\n        // 游戏逻辑服信息\n        var collect = balancedManager\n                .getLogicBalanced()\n                .listBrokerClientRegion()\n                .stream()\n                .map(brokerClientRegion -> {\n\n                    String tag = brokerClientRegion.getTag();\n                    int count = brokerClientRegion.count();\n\n                    return new BrokerClientNodeInfo(tag, count);\n                })\n                .collect(Collectors.toList());\n\n        // 对外服信息\n        int externalCount = balancedManager.getExternalLoadBalanced().count();\n\n        BrokerClientNodeInfo externalNodeInfo = new BrokerClientNodeInfo(\"external\", externalCount);\n        collect.add(externalNodeInfo);\n\n        String info = collect.stream()\n                .map(BrokerClientNodeInfo::toString)\n                .collect(Collectors.joining(\"\\n\\t\", \"\\n\\t\", \"\"));\n\n        int port = brokerServer.getPort();\n\n        var gameBrokerServer = Bundle.getMessage(MessageKey.gameBrokerServer);\n        log.info(\"{}:{} --- gameLogicServerList: {}\", gameBrokerServer, port, info);\n    }\n\n    private record BrokerClientNodeInfo(String tag, int count) {\n        @Override\n        public String toString() {\n            var gameServerAmount = Bundle.getMessage(MessageKey.gameServerAmount);\n\n            return \"{\" +\n                   gameServerAmount + \":\" + count +\n                   \", tag:'\" + tag + '\\'' +\n                   '}';\n        }\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/kit/EndPointClientIdKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.kit;\n\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.common.kit.ArrayKit;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-06\n */\n@UtilityClass\npublic class EndPointClientIdKit {\n    public void endPointClientId(HeadMetadata headMetadata, CmdRegions cmdRegions) {\n\n        // 玩家绑定的游戏逻辑服列表\n        int[] bindingLogicServerIds = headMetadata.getBindingLogicServerIds();\n        if (ArrayKit.isEmpty(bindingLogicServerIds)) {\n            return;\n        }\n\n        int cmdMerge = headMetadata.getCmdMerge();\n        int pointLogicServerId = cmdRegions.endPointLogicServerId(cmdMerge, bindingLogicServerIds);\n        headMetadata.setEndPointClientId(pointLogicServerId);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/BroadcastMessageBrokerProcessor.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BroadcastMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 把逻辑服的广播转发到对外服\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.MsgTransferTopic)\npublic final class BroadcastMessageBrokerProcessor extends AbstractAsyncUserProcessor<BroadcastMessage>\n        implements BrokerServerAware {\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BroadcastMessage broadcastMessage) {\n\n        if (IoGameGlobalConfig.broadcastLog) {\n            log.info(\"Broadcast 网关 广播消息到对外服务器 {}\", broadcastMessage);\n        }\n\n        BrokerExternalKit.sendMessageToExternal(this.brokerServer, broadcastMessage);\n    }\n\n    @Override\n    public String interest() {\n        return BroadcastMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/BroadcastOrderMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BroadcastOrderMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.ExecutorKit;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * 把逻辑服的广播 顺序的 转发到对外服\n * <pre>\n *     只使用了一个线程\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-07-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.MsgTransferTopic)\npublic final class BroadcastOrderMessageBrokerProcessor extends AsyncUserProcessor<BroadcastOrderMessage>\n        implements BrokerServerAware {\n    BrokerServer brokerServer;\n    final ExecutorService executorService = ExecutorKit.newSingleThreadExecutor(\"BroadcastOrderBroker\");\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BroadcastOrderMessage broadcastOrderMessage) {\n        if (IoGameGlobalConfig.broadcastLog) {\n            log.info(\"Broadcast 网关 顺序的 广播消息到对外服务器 {}\", broadcastOrderMessage);\n        }\n\n        BrokerExternalKit.sendMessageToExternal(this.brokerServer, broadcastOrderMessage);\n    }\n\n\n    @Override\n    public Executor getExecutor() {\n        return executorService;\n    }\n\n    @Override\n    public String interest() {\n        return BroadcastOrderMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/BrokerClientItemConnectMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientItemConnectMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * BrokerClientItemConnect 首次连接到游戏网关\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\n@Setter\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic final class BrokerClientItemConnectMessageBrokerProcessor extends AbstractAsyncUserProcessor<BrokerClientItemConnectMessage> {\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BrokerClientItemConnectMessage request) {\n        log.debug(\"bizCtx.getRemoteAddress() : {}\", bizCtx.getRemoteAddress());\n    }\n\n    @Override\n    public String interest() {\n        return BrokerClientItemConnectMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/BrokerExternalKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BroadcastMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.ExternalBrokerClientLoadBalanced;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.stream.Stream;\n\n/**\n * 工具类：把游戏逻辑服的广播转发到游戏对外服\n *\n * @author 渔民小镇\n * @date 2022-05-28\n */\n@Slf4j\n@UtilityClass\npublic class BrokerExternalKit {\n    /**\n     * 将数据发送给游戏对外服\n     *\n     * @param brokerServer     游戏网关\n     * @param broadcastMessage 数据\n     */\n    public void sendMessageToExternal(BrokerServer brokerServer, BroadcastMessage broadcastMessage) {\n        // 指定的游戏对外服 id\n        ResponseMessage responseMessage = broadcastMessage.getResponseMessage();\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        int sourceClientId = headMetadata.getSourceClientId();\n\n        // 游戏对外服相关\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        var externalLoadBalanced = balancedManager.getExternalLoadBalanced();\n\n        // 没有指定游戏对外服 id，全部转发\n        if (sourceClientId == 0) {\n            List<BrokerClientProxy> list = externalLoadBalanced.listBrokerClientProxy();\n            sendMessage(list, broadcastMessage);\n            return;\n        }\n\n        // 指定了游戏对外服\n        Stream<BrokerClientProxy> stream = streamToggle(sourceClientId, externalLoadBalanced);\n        Consumer<BrokerClientProxy> consumer = consumer(broadcastMessage);\n        stream.forEach(consumer);\n    }\n\n    /**\n     * 将数据发送给所有的游戏对外服\n     *\n     * @param brokerServer 游戏网关\n     * @param message      message\n     */\n    public void sendMessageToExternals(BrokerServer brokerServer, Object message) {\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        var externalLoadBalanced = balancedManager.getExternalLoadBalanced();\n        List<BrokerClientProxy> list = externalLoadBalanced.listBrokerClientProxy();\n        sendMessage(list, message);\n    }\n\n    /**\n     * 得到 Stream，根据 Broker（游戏网关）转发消息容错配置\n     * <pre>\n     *     更详细的说明可以看 {@link IoGameGlobalConfig#brokerSniperToggleAK47}\n     * </pre>\n     *\n     * @param sourceClientId       游戏对外服 id\n     * @param externalLoadBalanced externalLoadBalanced\n     * @return 得到 Stream\n     */\n    Stream<BrokerClientProxy> streamToggle(int sourceClientId, ExternalBrokerClientLoadBalanced externalLoadBalanced) {\n        List<BrokerClientProxy> list = externalLoadBalanced.listBrokerClientProxy();\n        Stream<BrokerClientProxy> stream = list.stream();\n\n        // 没有指定游戏对外服 id，不增加任何过虑条件\n        if (sourceClientId == 0) {\n            return stream;\n        }\n\n        if (IoGameGlobalConfig.brokerSniperToggleAK47) {\n            // 当为 true 时，开启容错机制，只有找到了指定的游戏对外服，才增加过虑条件\n            if (externalLoadBalanced.contains(sourceClientId)) {\n                stream = stream.filter(brokerClientProxy -> brokerClientProxy.getIdHash() == sourceClientId);\n            }\n        } else {\n            // 当为 false 时，表示关闭容错机制，直接增加过虑条件\n            stream = stream.filter(brokerClientProxy -> brokerClientProxy.getIdHash() == sourceClientId);\n        }\n\n        return stream;\n    }\n\n    private void sendMessage(List<BrokerClientProxy> list, Object message) {\n        Consumer<BrokerClientProxy> consumer = consumer(message);\n        list.forEach(consumer);\n    }\n\n    private Consumer<BrokerClientProxy> consumer(Object message) {\n        return brokerClientProxy -> {\n            try {\n                //  转发到游戏对外服务器\n                brokerClientProxy.oneway(message);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionCloseEventBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerClientModulesAware;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.bolt.broker.server.kit.BrokerPrintKit;\nimport com.iohao.game.bolt.broker.server.service.BrokerClientModules;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic final class ConnectionCloseEventBrokerProcessor implements ConnectionEventProcessor,\n        BrokerServerAware, BrokerClientModulesAware, CmdRegionsAware {\n\n    BrokerServer brokerServer;\n    BrokerClientModules brokerClientModules;\n    CmdRegions cmdRegions;\n\n    @Override\n    public void onEvent(String remoteAddress, Connection connection) {\n        Objects.requireNonNull(connection);\n\n        BalancedManager balancedManager = this.brokerServer.getBalancedManager();\n        // 当前下线的逻辑服\n        BrokerClientProxy brokerClientProxy = balancedManager.remove(remoteAddress);\n\n        Optional.ofNullable(brokerClientProxy).ifPresent(proxy -> {\n            if (IoGameGlobalConfig.openLog) {\n                log.info(\"Broker ConnectionEventType:【{}】，remoteAddress:【{}】，brokerClientProxy:【{}】，Connection:【{}】\",\n                        ConnectionEventType.CLOSE, remoteAddress, brokerClientProxy, connection\n                );\n\n                BrokerPrintKit.print(this.brokerServer);\n            }\n\n            String id = proxy.getId();\n            BrokerClientModuleMessage moduleMessage = this.brokerClientModules.removeById(id);\n\n            // 在集群下，可能为 null，因为存在 127、192 的问题\n            if (Objects.isNull(moduleMessage)) {\n                return;\n            }\n\n            var context = new LineKit.Context(brokerServer, brokerClientModules, cmdRegions, moduleMessage);\n\n            LineKit.offline(context);\n        });\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionEventBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.RequestBrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * broker 连接 event\n * <pre>\n *     see RequestBrokerClientModuleMessageClientProcessor.java\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic class ConnectionEventBrokerProcessor implements ConnectionEventProcessor, BrokerServerAware {\n    final AtomicInteger connectTimes = new AtomicInteger();\n    final AtomicBoolean connected = new AtomicBoolean();\n    Connection connection;\n    String remoteAddress;\n    final CountDownLatch latch = new CountDownLatch(1);\n\n    @Setter\n    BrokerServer brokerServer;\n\n    static final RequestBrokerClientModuleMessage requestBrokerClientModuleMessage = new RequestBrokerClientModuleMessage();\n\n    @Override\n    public void onEvent(String remoteAddress, Connection conn) {\n        extractedPrint(remoteAddress, conn);\n        Objects.requireNonNull(remoteAddress);\n        doCheckConnection(conn);\n        this.remoteAddress = remoteAddress;\n        this.connection = conn;\n        connected.set(true);\n        connectTimes.incrementAndGet();\n        latch.countDown();\n\n        int withNo = brokerServer.getWithNo();\n        requestBrokerClientModuleMessage.setWithNo(withNo);\n\n        //  通知客户端发送模块信息\n        try {\n            brokerServer.getRpcServer().oneway(conn, requestBrokerClientModuleMessage);\n        } catch (RemotingException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    private static void extractedPrint(String remoteAddress, Connection conn) {\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"Broker ConnectionEventType:【{}】 remoteAddress:【{}】，Connection:【{}】\",\n                    ConnectionEventType.CONNECT, remoteAddress, conn\n            );\n        }\n    }\n\n    private void doCheckConnection(Connection conn) {\n        Objects.requireNonNull(conn);\n        Objects.requireNonNull(conn.getPoolKeys());\n        Objects.requireNonNull(conn.getChannel());\n        Objects.requireNonNull(conn.getUrl());\n        Objects.requireNonNull(conn.getChannel().attr(Connection.CONNECTION).get());\n    }\n\n    public boolean isConnected() throws InterruptedException {\n        latch.await();\n        return this.connected.get();\n    }\n\n    public int getConnectTimes() throws InterruptedException {\n        latch.await();\n        return this.connectTimes.get();\n    }\n\n    public Connection getConnection() throws InterruptedException {\n        latch.await();\n        return this.connection;\n    }\n\n    public String getRemoteAddress() throws InterruptedException {\n        latch.await();\n        return this.remoteAddress;\n    }\n\n    public void reset() {\n        this.connectTimes.set(0);\n        this.connected.set(false);\n        this.connection = null;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionExceptionEventBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic final class ConnectionExceptionEventBrokerProcessor implements ConnectionEventProcessor {\n    @Override\n    public void onEvent(String remoteAddress, Connection connection) {\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"Broker ConnectionEventType:【{}】 remoteAddress:【{}】，Connection:【{}】\",\n                    ConnectionEventType.EXCEPTION, remoteAddress, connection\n            );\n        }\n\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionFailedEventBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic final class ConnectionFailedEventBrokerProcessor implements ConnectionEventProcessor {\n    @Override\n    public void onEvent(String remoteAddress, Connection connection) {\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"Broker ConnectionEventType:【{}】 remoteAddress:【{}】，Connection:【{}】\",\n                    ConnectionEventType.CONNECT_FAILED, remoteAddress, connection\n            );\n        }\n\n    }\n}"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/EndPointLogicServerMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.protocol.processor.EndPointLogicServerMessage;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport lombok.Setter;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-28\n */\n@Setter\npublic final class EndPointLogicServerMessageBrokerProcessor extends AbstractAsyncUserProcessor<EndPointLogicServerMessage>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, EndPointLogicServerMessage request) {\n        BrokerExternalKit.sendMessageToExternals(this.brokerServer, request);\n    }\n\n    @Override\n    public String interest() {\n        return EndPointLogicServerMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/EventBusMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.eventbus.EventBrokerClientMessage;\nimport com.iohao.game.action.skeleton.eventbus.EventBusMessage;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientType;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Collection;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * 分布式事件总线 broker\n *\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@Slf4j\n@Setter\npublic class EventBusMessageBrokerProcessor extends AbstractAsyncUserProcessor<EventBusMessage>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, final EventBusMessage eventBusMessage) {\n\n        Consumer<BrokerClientProxy> consumer = client -> {\n            try {\n                client.oneway(eventBusMessage);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n        };\n\n        BalancedManager balancedManager = this.brokerServer.getBalancedManager();\n        Collection<EventBrokerClientMessage> eventBrokerClientMessageSet = eventBusMessage.getEventBrokerClientMessages();\n\n        for (EventBrokerClientMessage eventBrokerClientMessage : eventBrokerClientMessageSet) {\n\n            String brokerClientId = eventBrokerClientMessage.getBrokerClientId();\n            BrokerClientType brokerClientType = BrokerClientType.valueOf(eventBrokerClientMessage.getBrokerClientType());\n\n            if (brokerClientType == BrokerClientType.LOGIC) {\n                // 转发给游戏逻辑服\n                balancedManager.getLogicBalanced()\n                        .listBrokerClientRegion()\n                        .stream()\n                        .flatMap(clientRegion -> clientRegion.listBrokerClientProxy().stream())\n                        .filter(brokerClientProxy -> Objects.equals(brokerClientProxy.getId(), brokerClientId))\n                        .forEach(consumer);\n            }\n\n            if (brokerClientType == BrokerClientType.EXTERNAL) {\n                // 转发给游戏对外服\n                balancedManager.getExternalLoadBalanced()\n                        .listBrokerClientProxy()\n                        .stream()\n                        .filter(brokerClientProxy -> Objects.equals(brokerClientProxy.getId(), brokerClientId))\n                        .forEach(consumer);\n            }\n        }\n    }\n\n    @Override\n    public String interest() {\n        return EventBusMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/InnerModuleMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.message.InnerModuleMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegion;\nimport com.iohao.game.bolt.broker.server.kit.EndPointClientIdKit;\nimport com.iohao.game.core.common.NetCommonKit;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 模块之间的请求处理\n * <pre>\n *     模块间的请求 - 游戏逻辑服与单个游戏逻辑服通信请求 - 有返回值（可跨进程）\n *\n *     如果不需要返回值的，see {@link InnerModuleVoidMessageBrokerProcessor}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Slf4j\npublic final class InnerModuleMessageBrokerProcessor extends AbstractAsyncUserProcessor<InnerModuleMessage>\n        implements BrokerServerAware, CmdRegionsAware {\n    @Setter\n    BrokerServer brokerServer;\n\n    @Setter\n    CmdRegions cmdRegions;\n\n    final ResponseMessage emptyResponseMessage;\n\n    public InnerModuleMessageBrokerProcessor() {\n        emptyResponseMessage = new ResponseMessage();\n        emptyResponseMessage.setHeadMetadata(new HeadMetadata());\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, InnerModuleMessage innerModuleMessage) {\n        // 模块之间的请求处理\n        var requestMessage = innerModuleMessage.getRequestMessage();\n        HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n\n        // 得到路由对应的逻辑服区域\n        int cmdMerge = headMetadata.getCmdMerge();\n\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        var logicBalanced = balancedManager.getLogicBalanced();\n        BrokerClientRegion brokerClientRegion = logicBalanced.getBrokerClientRegion(cmdMerge);\n\n        if (brokerClientRegion == null) {\n            extractedError(asyncCtx, requestMessage);\n            return;\n        }\n\n        EndPointClientIdKit.endPointClientId(headMetadata, this.cmdRegions);\n\n        // 从游戏逻辑服区域中查找一个游戏逻辑服，用于处理请求\n        BrokerClientProxy brokerClientProxy = brokerClientRegion.getBrokerClientProxy(headMetadata);\n\n        if (brokerClientProxy == null) {\n            extractedError(asyncCtx, requestMessage);\n            return;\n        }\n\n        NetCommonKit.executeVirtual(() -> {\n            try {\n                // 请求方请求其它服务器得到的响应数据\n                ResponseMessage responseMessage = brokerClientProxy.invokeSync(requestMessage);\n                // 将响应数据给回请求方\n                asyncCtx.sendResponse(responseMessage);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n        });\n    }\n\n    private void extractedError(AsyncContext asyncCtx, RequestMessage requestMessage) {\n        ResponseMessage responseMessage = requestMessage.createResponseMessage();\n        responseMessage.setError(ActionErrorEnum.cmdInfoErrorCode);\n        // 将响应数据给回请求方\n        asyncCtx.sendResponse(responseMessage);\n    }\n\n    @Override\n    public String interest() {\n        return InnerModuleMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/InnerModuleRequestCollectExternalMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalItemMessage;\nimport com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalMessage;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.ExternalBrokerClientLoadBalanced;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.common.kit.CompletableFutureKit;\nimport com.iohao.game.core.common.NetCommonKit;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.stream.Stream;\n\n/**\n * 游戏逻辑服访问游戏对外服，同时访问多个游戏对外服 - 请求\n * <pre>\n *     游戏逻辑服访问游戏对外服，因为只有游戏对外服持有这些数据\n *     把多个游戏对外服的结果聚合在一起\n *\n *     <a href=\"https://iohao.github.io/game/docs/communication/external_biz_region\">访问游戏对外服与扩展</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\n@Setter\n@Slf4j\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class InnerModuleRequestCollectExternalMessageBrokerProcessor extends AbstractAsyncUserProcessor<RequestCollectExternalMessage>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestCollectExternalMessage requestCollectMessage) {\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        var balanced = balancedManager.getExternalLoadBalanced();\n\n        // 并行调用多个游戏对外服\n        var futureList = this.listFuture(requestCollectMessage, balanced);\n        CompletableFutureKit.sequenceAsync(futureList).thenAccept(messageList -> {\n            // 多个游戏对外服的响应结果\n            ResponseCollectExternalMessage responseCollectMessage = new ResponseCollectExternalMessage();\n            responseCollectMessage.setMessageList(messageList);\n\n            // 将响应数据给回请求方\n            asyncCtx.sendResponse(responseCollectMessage);\n        });\n    }\n\n    private List<CompletableFuture<ResponseCollectExternalItemMessage>> listFuture(\n            RequestCollectExternalMessage requestCollectMessage,\n            ExternalBrokerClientLoadBalanced externalLoadBalanced) {\n\n        // 游戏对外服 id\n        int sourceClientId = requestCollectMessage.getSourceClientId();\n        Stream<BrokerClientProxy> stream = BrokerExternalKit.streamToggle(sourceClientId, externalLoadBalanced);\n\n        return stream.map(brokerClientProxy -> CompletableFuture.supplyAsync(() -> {\n            ResponseCollectExternalItemMessage itemMessage;\n\n            try {\n                // 请求方请求其它服务器得到的响应数据\n                itemMessage = brokerClientProxy.invokeSync(requestCollectMessage);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n                return null;\n            }\n\n            // 有错误或没有数据的，就不做处理了，意义不大\n            if (itemMessage == null) {\n                return null;\n            }\n\n            String logicServerId = brokerClientProxy.getId();\n            // 得到一个逻辑服的结果\n            return itemMessage.setLogicServerId(logicServerId);\n        }, NetCommonKit.getVirtualExecutor())).toList();\n    }\n\n    @Override\n    public String interest() {\n        return RequestCollectExternalMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/InnerModuleRequestCollectMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.protocol.collect.RequestCollectMessage;\nimport com.iohao.game.action.skeleton.protocol.collect.ResponseCollectItemMessage;\nimport com.iohao.game.action.skeleton.protocol.collect.ResponseCollectMessage;\nimport com.iohao.game.bolt.broker.cluster.BrokerClusterManager;\nimport com.iohao.game.bolt.broker.cluster.BrokerRunModeEnum;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegion;\nimport com.iohao.game.common.kit.CompletableFutureKit;\nimport com.iohao.game.core.common.NetCommonKit;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * 模块之间的访问，访问同类型的多个逻辑服\n * <pre>\n *     如： 模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据\n *     是把多个相同逻辑服结果聚合在一起\n *\n *     文档\n *          <a href=\"https://iohao.github.io/game/docs/communication/request_multiple_response\">request/multiple_response</a>\n * </pre>\n *\n * <pre>\n *     处理方式使用 CompletableFuture、ForkJoinPool 并行访问所有相同有逻辑服。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-22\n */\n@Slf4j\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class InnerModuleRequestCollectMessageBrokerProcessor extends AbstractAsyncUserProcessor<RequestCollectMessage>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestCollectMessage requestCollectMessage) {\n\n        RequestMessage requestMessage = requestCollectMessage.getRequestMessage();\n        HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n        int cmdMerge = headMetadata.getCmdMerge();\n\n        // 得到路由对应的逻辑服区域\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        var logicBalanced = balancedManager.getLogicBalanced();\n        BrokerClientRegion brokerClientRegion = logicBalanced.getBrokerClientRegion(cmdMerge);\n\n        if (brokerClientRegion == null) {\n            // 响应结果\n            var responseCollectMessage = new ResponseCollectMessage();\n            responseCollectMessage.setError(ActionErrorEnum.cmdInfoErrorCode);\n            // 将响应数据给回请求方\n            asyncCtx.sendResponse(responseCollectMessage);\n            return;\n        }\n\n        // 并行调用多个逻辑服\n        var futureList = this.listFuture(requestMessage, brokerClientRegion);\n        CompletableFutureKit.sequenceAsync(futureList).thenAcceptAsync(messageList -> {\n            var responseCollectMessage = new ResponseCollectMessage();\n            responseCollectMessage.setMessageList(messageList);\n\n            // 将响应数据给回请求方\n            asyncCtx.sendResponse(responseCollectMessage);\n\n            print(responseCollectMessage);\n        }, NetCommonKit.getVirtualExecutor());\n    }\n\n    private void print(ResponseCollectMessage responseCollectMessage) {\n        if (IoGameGlobalConfig.requestResponseLog) {\n\n            if (this.brokerServer.getBrokerRunMode() != BrokerRunModeEnum.CLUSTER) {\n                return;\n            }\n\n            int port = brokerServer.getPort();\n            String brokerId = brokerServer.getBrokerId();\n            BrokerClusterManager brokerClusterManager = brokerServer.getBrokerClusterManager();\n            int gossipListenPort = brokerClusterManager.getGossipListenPort();\n            log.info(\"\\n port [{}] gossipListenPort [{}] id [{}] \\n responseAggregationMessage : {}\"\n                    , port\n                    , gossipListenPort\n                    , brokerId\n                    , responseCollectMessage);\n        }\n    }\n\n    private List<CompletableFuture<ResponseCollectItemMessage>> listFuture(RequestMessage requestMessage\n            , BrokerClientRegion brokerClientRegion) {\n\n        // 逻辑服列表 stream；异步请求逻辑服\n        var stream = brokerClientRegion.listBrokerClientProxy().stream();\n        return stream.map(brokerClientProxy -> CompletableFuture.supplyAsync(() -> {\n            ResponseMessage responseMessage;\n\n            try {\n                // 请求方请求其它服务器得到的响应数据\n                responseMessage = brokerClientProxy.invokeSync(requestMessage);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n                return null;\n            }\n\n            byte[] data;\n            // 有错误或没有数据的，就不做处理了，意义不大\n            if (responseMessage == null\n                || responseMessage.hasError()\n                || (data = responseMessage.getData()) == null\n                || data.length == 0) {\n                return null;\n            }\n\n            // 逻辑服 id\n            String logicServerId = brokerClientProxy.getId();\n            // 得到一个逻辑服的结果\n            return new ResponseCollectItemMessage(logicServerId, responseMessage);\n        }, NetCommonKit.getVirtualExecutor())).toList();\n    }\n\n    @Override\n    public String interest() {\n        return RequestCollectMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/InnerModuleVoidMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.message.InnerModuleVoidMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegion;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 模块之间的请求处理\n * <pre>\n *     模块间的请求 - 游戏逻辑服与单个游戏逻辑服通信请求 - 无返回值（可跨进程）\n *\n *     如果需要返回值的，see {@link InnerModuleMessageBrokerProcessor}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-06-07\n */\n@Slf4j\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class InnerModuleVoidMessageBrokerProcessor extends AbstractAsyncUserProcessor<InnerModuleVoidMessage>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, InnerModuleVoidMessage innerModuleMessage) {\n        // 模块之间的请求处理\n        var requestMessage = innerModuleMessage.getRequestMessage();\n        HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n\n        // 得到路由对应的逻辑服区域\n        int cmdMerge = headMetadata.getCmdMerge();\n\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        var logicBalanced = balancedManager.getLogicBalanced();\n        BrokerClientRegion brokerClientRegion = logicBalanced.getBrokerClientRegion(cmdMerge);\n\n        if (brokerClientRegion == null) {\n            return;\n        }\n\n        // 逻辑服的负载均衡\n        BrokerClientProxy brokerClientProxy = brokerClientRegion.getBrokerClientProxy(headMetadata);\n\n        if (brokerClientProxy == null) {\n            return;\n        }\n\n        try {\n            // 请求方请求其它服务器得到的响应数据\n            brokerClientProxy.oneway(requestMessage);\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public String interest() {\n        return InnerModuleVoidMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/LineKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.exception.RemotingException;\nimport com.alipay.remoting.rpc.RpcServer;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientType;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientOfflineMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientOnlineMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.service.BrokerClientModules;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport com.iohao.game.core.common.cmd.BrokerClientId;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Slf4j\n@UtilityClass\nclass LineKit {\n    final long executorIndex = IoGameGlobalConfig.InternalConfig.executorIndex;\n\n    record Context(BrokerServer brokerServer\n            , BrokerClientModules brokerClientModules\n            , CmdRegions cmdRegions\n            , BrokerClientModuleMessage moduleMessage) {\n    }\n\n    void online(Context context) {\n        // 避免并发，使用同一个执行器\n        ExecutorRegionKit.getSimpleThreadExecutor(executorIndex).executeTry(() -> {\n            // online logic\n            internalOnlineNew(context);\n        });\n    }\n\n    private void internalOnlineNew(Context context) {\n        // 当前新上线的逻辑服\n        BrokerClientModuleMessage moduleMessage = context.moduleMessage();\n\n        extractedCmdRegions(context, moduleMessage);\n\n        // 当前逻辑服的信息\n        BrokerClientOnlineMessage onlineMessage = BrokerClientOnlineMessage.of(moduleMessage);\n\n        streamOtherClient(context).forEach(theModuleMessage -> {\n            try {\n                RpcServer rpcServer = context.brokerServer().getRpcServer();\n\n                // 将当前逻辑服的信息发送给其他逻辑服（游戏逻辑服和游戏对外服）\n                String theModuleAddress = theModuleMessage.getAddress();\n                rpcServer.oneway(theModuleAddress, onlineMessage);\n\n                // 拉取所有的逻辑服信息给自己（也就是当前新上线的逻辑服）\n                BrokerClientOnlineMessage onlineMsg = BrokerClientOnlineMessage.of(theModuleMessage);\n                String address = moduleMessage.getAddress();\n                rpcServer.oneway(address, onlineMsg);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n        });\n    }\n\n    private void extractedCmdRegions(Context context, BrokerClientModuleMessage moduleMessage) {\n        BrokerClientType brokerClientType = moduleMessage.getBrokerClientType();\n        if (brokerClientType == BrokerClientType.LOGIC) {\n            // 如果新上线的是游戏逻辑服，将路由信息保存一份\n            CmdRegions cmdRegions = context.cmdRegions();\n            cmdRegions.loading(moduleMessage);\n        }\n    }\n\n    void offline(Context context) {\n        ExecutorRegionKit.getSimpleThreadExecutor(executorIndex).executeTry(() -> {\n            // offline logic\n            internalOfflineNew(context);\n        });\n    }\n\n    private static void internalOfflineNew(Context context) {\n        // 当前下线的逻辑服\n        BrokerClientModuleMessage moduleMessage = getBrokerClientModuleMessage(context);\n\n        // 通知其他逻辑服，当前逻辑服下线了\n        BrokerClientOfflineMessage offlineMessage = BrokerClientOfflineMessage.of(moduleMessage);\n\n        streamOtherClient(context).forEach(theModuleMessage -> {\n            try {\n                String address = theModuleMessage.getAddress();\n                context.brokerServer().getRpcServer().oneway(address, offlineMessage);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n        });\n\n    }\n\n    private BrokerClientModuleMessage getBrokerClientModuleMessage(Context context) {\n        BrokerClientModuleMessage moduleMessage = context.moduleMessage();\n\n        BrokerClientType brokerClientType = moduleMessage.getBrokerClientType();\n        if (brokerClientType == BrokerClientType.LOGIC) {\n            CmdRegions cmdRegions = context.cmdRegions();\n            String id = moduleMessage.getId();\n            int idHash = moduleMessage.getIdHash();\n            BrokerClientId brokerClientId = new BrokerClientId(idHash, id);\n            // 游戏逻辑服的路由数据\n            cmdRegions.unLoading(brokerClientId);\n        }\n\n        return moduleMessage;\n    }\n\n    Stream<BrokerClientModuleMessage> streamOtherClient(Context context) {\n        BrokerClientModuleMessage moduleMessage = context.moduleMessage();\n\n        return context.brokerClientModules()\n                .listBrokerClientModuleMessage()\n                .stream()\n                // 排除自己\n                .filter(message -> !Objects.equals(message.getId(), moduleMessage.getId()));\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/PulseSignalRequestBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.pulse.message.SignalType;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.common.kit.exception.ThrowKit;\n\nimport java.util.function.Consumer;\n\n/**\n * 将脉冲信号发送给对应的逻辑服\n *\n * @author 渔民小镇\n * @date 2023-04-22\n */\npublic final class PulseSignalRequestBrokerProcessor extends AbstractAsyncUserProcessor<PulseSignalRequest>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void setBrokerServer(BrokerServer brokerServer) {\n        this.brokerServer = brokerServer;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, PulseSignalRequest request) {\n\n        BalancedManager balancedManager = this.brokerServer.getBalancedManager();\n\n        Consumer<BrokerClientProxy> consumer = client -> {\n            try {\n                client.oneway(request);\n            } catch (RemotingException | InterruptedException e) {\n                ThrowKit.ofRuntimeException(e);\n            }\n        };\n\n        if (request.containsSignalType(SignalType.external)) {\n            // 转发给游戏对外服\n            balancedManager.getExternalLoadBalanced()\n                    .listBrokerClientProxy()\n                    .forEach(consumer);\n        }\n\n        if (request.containsSignalType(SignalType.logic)) {\n            // 转发给游戏逻辑服\n            balancedManager.getLogicBalanced()\n                    .listBrokerClientRegion()\n                    .stream()\n                    .flatMap(clientRegion -> clientRegion.listBrokerClientProxy().stream())\n                    .forEach(consumer);\n        }\n    }\n\n    @Override\n    public String interest() {\n        return PulseSignalRequest.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/PulseSignalResponseBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalResponse;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.LogicBrokerClientLoadBalanced;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-22\n */\n@Slf4j\npublic final class PulseSignalResponseBrokerProcessor extends AbstractAsyncUserProcessor<PulseSignalResponse>\n        implements BrokerServerAware {\n    BrokerServer brokerServer;\n\n    @Override\n    public void setBrokerServer(BrokerServer brokerServer) {\n        this.brokerServer = brokerServer;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, PulseSignalResponse response) {\n        int sourceClientId = response.getSourceClientId();\n\n        BalancedManager balancedManager = this.brokerServer.getBalancedManager();\n        LogicBrokerClientLoadBalanced logicBalanced = balancedManager.getLogicBalanced();\n        // 根据 sourceClientId 获取对应的游戏逻辑服\n        BrokerClientProxy client = logicBalanced.getBrokerClientProxyByIdHash(sourceClientId);\n        if (Objects.isNull(client)) {\n            return;\n        }\n\n        try {\n            client.oneway(response);\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public String interest() {\n        return PulseSignalResponse.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/RegisterBrokerClientModuleMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.bolt.broker.cluster.BrokerClusterManager;\nimport com.iohao.game.bolt.broker.cluster.BrokerRunModeEnum;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClusterMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerMessage;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerClientModulesAware;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.kit.BrokerPrintKit;\nimport com.iohao.game.bolt.broker.server.service.BrokerClientModules;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\n\n/**\n * 模块注册\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic final class RegisterBrokerClientModuleMessageBrokerProcessor extends AsyncUserProcessor<BrokerClientModuleMessage>\n        implements BrokerServerAware, BrokerClientModulesAware, CmdRegionsAware {\n\n    BrokerServer brokerServer;\n    BrokerClientModules brokerClientModules;\n    CmdRegions cmdRegions;\n    AtomicBoolean fixedRateFlag = new AtomicBoolean(false);\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BrokerClientModuleMessage brokerClientModuleMessage) {\n        brokerClientModules.add(brokerClientModuleMessage);\n\n        String remoteAddress = bizCtx.getRemoteAddress();\n        brokerClientModuleMessage.setAddress(remoteAddress);\n\n        // 注册到负载器中\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        balancedManager.register(brokerClientModuleMessage);\n\n        // 发送网关集群消息给客户端 （逻辑服）\n        if (brokerServer.getBrokerRunMode() == BrokerRunModeEnum.CLUSTER) {\n            this.sendClusterMessage(bizCtx);\n\n            this.printCluster();\n        }\n\n        print(brokerClientModuleMessage);\n\n        var context = new LineKit.Context(brokerServer, brokerClientModules, cmdRegions, brokerClientModuleMessage);\n        LineKit.online(context);\n    }\n\n    private void printCluster(BrokerClusterMessage brokerClusterMessage) {\n        if (brokerServer.getBrokerRunMode() != BrokerRunModeEnum.CLUSTER) {\n            return;\n        }\n\n        if (IoGameGlobalConfig.isBrokerClusterLog()) {\n            String message = brokerClusterMessage\n                    .getBrokerMessageList()\n                    .stream()\n                    .map(BrokerMessage::toString)\n                    .collect(Collectors.joining(\"\\n\"));\n\n            log.info(\"\\n游戏网关端口: [{}] --  集群数量[{}] - 详细：\\n[{}]\"\n                    , this.brokerServer.getPort()\n                    , brokerClusterMessage.count()\n                    , message);\n        }\n    }\n\n    private void sendClusterMessage(BizContext bizCtx) {\n\n        BrokerClusterManager brokerClusterManager = brokerServer.getBrokerClusterManager();\n        BrokerClusterMessage brokerClusterMessage = brokerClusterManager.getBrokerClusterMessage();\n\n        this.printCluster(brokerClusterMessage);\n\n        try {\n            this.brokerServer.getRpcServer().oneway(bizCtx.getConnection(), brokerClusterMessage);\n        } catch (RemotingException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    private void print(BrokerClientModuleMessage brokerClientModuleMessage) {\n\n        if (IoGameGlobalConfig.openLog) {\n            var port = this.brokerServer.getPort();\n            var brokerClientRegistrationMessage = Bundle.getMessage(MessageKey.brokerClientRegistrationMessage);\n            var gameBrokerServer = Bundle.getMessage(MessageKey.gameBrokerServer);\n\n            log.info(\"{} --- {} port: [{}] --- {}\",\n                    brokerClientRegistrationMessage,\n                    gameBrokerServer, port,\n                    brokerClientModuleMessage);\n        }\n\n        // print\n        BrokerPrintKit.print(this.brokerServer);\n    }\n\n    private void printCluster() {\n        if (brokerServer.getBrokerRunMode() != BrokerRunModeEnum.CLUSTER) {\n            return;\n        }\n\n        if (!IoGameGlobalConfig.isBrokerClusterFixedRateLog()) {\n            return;\n        }\n\n        if (fixedRateFlag.get()) {\n            return;\n        }\n\n        if (fixedRateFlag.compareAndSet(false, true)) {\n            TaskKit.runIntervalMinute(() -> {\n                BrokerPrintKit.print(brokerServer);\n\n                BrokerClusterManager brokerClusterManager = brokerServer.getBrokerClusterManager();\n                BrokerClusterMessage brokerClusterMessage = brokerClusterManager.getBrokerClusterMessage();\n\n                printCluster(brokerClusterMessage);\n            }, 1);\n        }\n    }\n\n    @Override\n    public String interest() {\n        return BrokerClientModuleMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/RequestMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.alipay.remoting.rpc.RpcServer;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.bolt.broker.core.aware.CmdRegionsAware;\nimport com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorSelectorAware;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.common.UserProcessorExecutorSelectorStrategy;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.ExternalBrokerClientLoadBalanced;\nimport com.iohao.game.bolt.broker.server.balanced.LogicBrokerClientLoadBalanced;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientRegion;\nimport com.iohao.game.bolt.broker.server.kit.EndPointClientIdKit;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.core.common.cmd.CmdRegions;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 对外服务器消息处理\n * <pre>\n *     接收真实用户的请求，把请求转发到逻辑服\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.MsgTransferTopic)\npublic final class RequestMessageBrokerProcessor extends AbstractAsyncUserProcessor<RequestMessage>\n        implements BrokerServerAware\n        , UserProcessorExecutorSelectorAware\n        , CmdRegionsAware {\n\n    BrokerServer brokerServer;\n\n    CmdRegions cmdRegions;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestMessage request) {\n        if (IoGameGlobalConfig.requestResponseLog) {\n            extractedPrint(request);\n        }\n\n        // 逻辑服的负载均衡\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        LogicBrokerClientLoadBalanced loadBalanced = balancedManager.getLogicBalanced();\n\n        // 得到路由对应的逻辑服区域\n        HeadMetadata headMetadata = request.getHeadMetadata();\n        BrokerClientRegion brokerClientRegion = loadBalanced.getBrokerClientRegion(headMetadata.getCmdMerge());\n\n        if (brokerClientRegion == null) {\n            //  通知对外服， 路由不存在\n            extractedNotRoute(bizCtx, request);\n            return;\n        }\n\n        EndPointClientIdKit.endPointClientId(headMetadata, this.cmdRegions);\n\n        headMetadata.setWithNo(this.brokerServer.getWithNo());\n        // 从游戏逻辑服区域中查找一个游戏逻辑服，用于处理请求\n        BrokerClientProxy brokerClientProxy = brokerClientRegion.getBrokerClientProxy(headMetadata);\n        if (brokerClientProxy == null) {\n            //  通知对外服， 路由不存在\n            extractedNotRoute(bizCtx, request);\n            return;\n        }\n\n        try {\n            brokerClientProxy.oneway(request);\n        } catch (RemotingException | InterruptedException | NullPointerException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    private void extractedPrint(RequestMessage request) {\n\n        log.info(\"游戏网关把对外服 请求 转发到逻辑服 : {}\", request);\n\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        ExternalBrokerClientLoadBalanced externalLoadBalanced = balancedManager.getExternalLoadBalanced();\n\n        for (BrokerClientProxy brokerClientProxy : externalLoadBalanced.listBrokerClientProxy()) {\n            log.info(\"brokerClientProxy : {}\", brokerClientProxy);\n        }\n    }\n\n    private void extractedNotRoute(BizContext bizCtx, RequestMessage requestMessage) {\n        // 路由不存在\n        Connection connection = bizCtx.getConnection();\n        ResponseMessage responseMessage = requestMessage.createResponseMessage();\n        HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n\n        ActionErrorEnum errorCode = ActionErrorEnum.cmdInfoErrorCode;\n        if (headMetadata.getOther() instanceof ActionErrorEnum theCode) {\n            errorCode = theCode;\n        }\n\n        responseMessage.setValidatorMsg(errorCode.getMsg())\n                .setResponseStatus(errorCode.getCode());\n\n        RpcServer rpcServer = brokerServer.getRpcServer();\n\n        try {\n            rpcServer.oneway(connection, responseMessage);\n        } catch (RemotingException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public void setUserProcessorExecutorSelector(UserProcessorExecutorSelectorStrategy executorSelector) {\n        this.setExecutorSelector(executorSelector);\n    }\n\n    @Override\n    public String interest() {\n        return RequestMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ResponseMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.ExternalBrokerClientLoadBalanced;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\n\n/**\n * 把逻辑服的响应转发到对外服\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Setter\n@Slf4j(topic = IoGameLogName.MsgTransferTopic)\npublic final class ResponseMessageBrokerProcessor extends AbstractAsyncUserProcessor<ResponseMessage>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, ResponseMessage responseMessage) {\n        if (IoGameGlobalConfig.requestResponseLog) {\n            log.info(\"把逻辑服的响应转发到对外服 {}\", responseMessage);\n        }\n\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        int sourceClientId = headMetadata.getSourceClientId();\n\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        ExternalBrokerClientLoadBalanced externalLoadBalanced = balancedManager.getExternalLoadBalanced();\n        BrokerClientProxy brokerClientProxy = externalLoadBalanced.get(sourceClientId);\n\n        if (Objects.isNull(brokerClientProxy)) {\n            log.warn(\"对外服不存在: [{}]\", sourceClientId);\n            return;\n        }\n\n        try {\n            // 转发 给 对外服务器\n            brokerClientProxy.oneway(responseMessage);\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    public String interest() {\n        return ResponseMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/SettingUserIdMessageBrokerProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.action.skeleton.protocol.login.SettingUserIdMessage;\nimport com.iohao.game.action.skeleton.protocol.login.SettingUserIdMessageResponse;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.aware.BrokerServerAware;\nimport com.iohao.game.bolt.broker.server.balanced.BalancedManager;\nimport com.iohao.game.bolt.broker.server.balanced.region.BrokerClientProxy;\nimport com.iohao.game.core.common.NetCommonKit;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 设置用户 id\n * <pre>\n *     用户 id 变更 （逻辑服 --> 网关 --> 对外服 --> 网关 --> 逻辑服）\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Slf4j\n@Setter\npublic final class SettingUserIdMessageBrokerProcessor extends AbstractAsyncUserProcessor<SettingUserIdMessage>\n        implements BrokerServerAware {\n\n    BrokerServer brokerServer;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, SettingUserIdMessage settingUserIdMessage) {\n        // 用户 id 变更\n        HeadMetadata headMetadata = settingUserIdMessage.getHeadMetadata();\n\n        // 得到对外服的 id （就是发起请求的对外服）\n        int sourceClientId = headMetadata.getSourceClientId();\n\n        BalancedManager balancedManager = brokerServer.getBalancedManager();\n        var externalLoadBalanced = balancedManager.getExternalLoadBalanced();\n\n        // 根据 sourceClientId 获取对应的对外服\n        final BrokerClientProxy brokerClientProxy = externalLoadBalanced.get(sourceClientId);\n\n        NetCommonKit.executeVirtual(() -> {\n            try {\n                // 转发给对外服, 并得到对外服的响应\n                SettingUserIdMessageResponse messageResponse = brokerClientProxy.invokeSync(settingUserIdMessage);\n                asyncCtx.sendResponse(messageResponse);\n            } catch (RemotingException | InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n        });\n    }\n\n    @Override\n    public String interest() {\n        return SettingUserIdMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/service/BrokerClientModules.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.service;\n\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\n\nimport java.util.Collection;\n\n/**\n * BrokerClient 模块信息\n *\n * @author 渔民小镇\n * @date 2023-05-01\n */\npublic interface BrokerClientModules {\n    void add(BrokerClientModuleMessage moduleMessage);\n\n    BrokerClientModuleMessage removeById(String id);\n\n    Collection<BrokerClientModuleMessage> listBrokerClientModuleMessage();\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/service/DefaultBrokerClientModules.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.server.service;\n\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Collection;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-01\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultBrokerClientModules implements BrokerClientModules {\n    NonBlockingHashMap<String, BrokerClientModuleMessage> moduleMessageMap = new NonBlockingHashMap<>();\n\n    @Override\n    public void add(BrokerClientModuleMessage moduleMessage) {\n        this.moduleMessageMap.put(moduleMessage.getId(), moduleMessage);\n    }\n\n    @Override\n    public BrokerClientModuleMessage removeById(String id) {\n        BrokerClientModuleMessage brokerClientModuleMessage = this.moduleMessageMap.get(id);\n        this.moduleMessageMap.remove(id);\n\n        return brokerClientModuleMessage;\n    }\n\n    @Override\n    public Collection<BrokerClientModuleMessage> listBrokerClientModuleMessage() {\n        return this.moduleMessageMap.values();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/test/java/com/iohao/game/bolt/broker/cluster/Gossip1Test.java",
    "content": "package com.iohao.game.bolt.broker.cluster;\n\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport org.junit.Test;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-16\n */\npublic class Gossip1Test {\n\n    @Test\n    public void a() throws InterruptedException {\n\n        // 种子节点\n        List<String> seedAddress = List.of(\n                \"127.0.0.1:30056\",\n                \"127.0.0.1:30057\"\n        );\n\n        int gossipListenPort = 30056;\n        int port = IoGameGlobalConfig.brokerPort;\n\n        BrokerClusterManager brokerClusterManager = new BrokerClusterManager();\n        brokerClusterManager.setGossipListenPort(gossipListenPort);\n        brokerClusterManager.setPort(port);\n        brokerClusterManager.setSeedAddress(seedAddress);\n\n        brokerClusterManager.start();\n\n        TimeUnit.MINUTES.sleep(22);\n    }\n}"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/test/java/com/iohao/game/bolt/broker/cluster/Gossip2Test.java",
    "content": "package com.iohao.game.bolt.broker.cluster;\n\nimport org.junit.Test;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-16\n */\npublic class Gossip2Test {\n\n    @Test\n    public void test() throws Exception {\n\n        // 种子节点\n        List<String> seedAddress = List.of(\n                \"127.0.0.1:30056\",\n                \"127.0.0.1:30057\"\n        );\n\n        int gossipListenPort = 30057;\n        int port = 10201;\n\n        BrokerClusterManager brokerClusterManager = new BrokerClusterManager();\n        brokerClusterManager.setGossipListenPort(gossipListenPort);\n        brokerClusterManager.setPort(port);\n        brokerClusterManager.setSeedAddress(seedAddress);\n\n        brokerClusterManager.start();\n\n//        brokerClusterManager.send();\n\n        TimeUnit.MINUTES.sleep(22);\n    }\n}"
  },
  {
    "path": "net-bolt/bolt-broker-server/src/test/java/com/iohao/game/bolt/broker/server/BrokerServerStandaloneTest.java",
    "content": "package com.iohao.game.bolt.broker.server;\n\nimport com.iohao.game.bolt.broker.cluster.BrokerRunModeEnum;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 单机网关服\n * <pre>\n *     {@link BrokerRunModeEnum#STANDALONE}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\npublic class BrokerServerStandaloneTest {\n    //    @Test\n    public void test() throws Exception {\n        // Broker Server （游戏网关服） 构建器\n        BrokerServerBuilder brokerServerBuilder = BrokerServer.newBuilder()\n                // broker （游戏网关）默认端口 10200\n                .port(IoGameGlobalConfig.brokerPort);\n\n        // 构建游戏网关\n        BrokerServer brokerServer = brokerServerBuilder.build();\n\n        // 启动 游戏网关\n        brokerServer.startup();\n\n        TimeUnit.SECONDS.sleep(1);\n    }\n}"
  },
  {
    "path": "net-bolt/bolt-client/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>bolt-client</artifactId>\n    <name>bolt-client for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>bolt-core</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/AbstractBrokerClientStartup.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client;\n\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.client.processor.*;\nimport com.iohao.game.bolt.broker.client.processor.connection.CloseConnectEventClientProcessor;\nimport com.iohao.game.bolt.broker.client.processor.connection.ConnectEventClientProcessor;\nimport com.iohao.game.bolt.broker.client.processor.connection.ConnectFailedEventClientProcessor;\nimport com.iohao.game.bolt.broker.client.processor.connection.ExceptionConnectEventClientProcessor;\nimport com.iohao.game.bolt.broker.core.GroupWith;\nimport com.iohao.game.bolt.broker.core.client.BrokerAddress;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientBuilder;\nimport com.iohao.game.bolt.broker.core.common.processor.pulse.PulseSignalRequestUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.processor.pulse.PulseSignalResponseUserProcessor;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\n\n/**\n * 逻辑服抽象类\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic abstract non-sealed class AbstractBrokerClientStartup implements BrokerClientStartup, GroupWith {\n    /** 连接 broker （游戏网关） 的地址 */\n    BrokerAddress brokerAddress;\n    /** 业务框架 */\n    BarSkeleton barSkeleton;\n    /**\n     * BoltBrokerClient 构建器\n     * <pre>\n     *     如果字段赋值了\n     *          就不会使用 {@link BrokerClientStartup#createBrokerClientBuilder()} 接口的值\n     *\n     *     如果字段没有赋值\n     *          就会使用 {@link BrokerClientStartup#createBrokerClientBuilder()} 接口的值\n     * </pre>\n     */\n    BrokerClientBuilder brokerClientBuilder;\n    int withNo;\n\n    @Override\n    public void connectionEventProcessor(BrokerClientBuilder brokerClientBuilder) {\n        brokerClientBuilder\n                .addConnectionEventProcessor(ConnectionEventType.CONNECT, ConnectEventClientProcessor::new)\n                .addConnectionEventProcessor(ConnectionEventType.CLOSE, CloseConnectEventClientProcessor::new)\n                .addConnectionEventProcessor(ConnectionEventType.CONNECT_FAILED, ConnectFailedEventClientProcessor::new)\n                .addConnectionEventProcessor(ConnectionEventType.EXCEPTION, ExceptionConnectEventClientProcessor::new);\n    }\n\n    @Override\n    public void registerUserProcessor(BrokerClientBuilder brokerClientBuilder) {\n        brokerClientBuilder\n                // 收到网关请求模块信息\n                .registerUserProcessor(RequestBrokerClientModuleMessageClientProcessor::new)\n                // broker （游戏网关）集群处理\n                .registerUserProcessor(BrokerClusterMessageClientProcessor::new)\n                // 业务请求处理器\n                .registerUserProcessor(RequestMessageClientProcessor::new)\n                // 脉冲信号请求接收\n                .registerUserProcessor(PulseSignalRequestUserProcessor::new)\n                // 脉冲信号响应接收\n                .registerUserProcessor(PulseSignalResponseUserProcessor::new)\n                // 分布式事件总线接收\n                .registerUserProcessor(EventBusMessageClientProcessor::new)\n                // 其他逻辑服的上线、下线消息接收\n                .registerUserProcessor(BrokerClientOfflineMessageLogicProcessor::new)\n                .registerUserProcessor(BrokerClientOnlineMessageLogicProcessor::new)\n        ;\n    }\n\n    /**\n     * 初始化一些配置到构建器中\n     * <pre>\n     *     这个方法的目的在于，先设置一些配置到 builder 中，后续有需要修改的部分配置在单独到 builder 中设置\n     * </pre>\n     *\n     * @return BoltBrokerClientBuilder\n     */\n    BrokerClientBuilder initConfig() {\n        IoGameBanner.me().init();\n        // 业务框架\n        this.barSkeleton = this.createBarSkeleton();\n        // 连接到游戏网关的地址\n        this.brokerAddress = this.createBrokerAddress();\n        // 构建器\n        if (Objects.isNull(this.brokerClientBuilder)) {\n            this.brokerClientBuilder = this.createBrokerClientBuilder();\n        }\n\n        Objects.requireNonNull(this.brokerClientBuilder, \"brokerClient 构建器必须要有\");\n\n        // 设置 config 配置信息到 BoltBrokerClientBuilder 中\n        this.brokerClientBuilder\n                .withNo(this.withNo)\n                .barSkeleton(this.barSkeleton)\n                .brokerAddress(this.brokerAddress);\n\n        // 添加连接处理器\n        this.connectionEventProcessor(this.brokerClientBuilder);\n        // 注册用户处理器\n        this.registerUserProcessor(this.brokerClientBuilder);\n\n        // 实验性功能\n        experiment();\n\n        return this.brokerClientBuilder;\n    }\n\n    /**\n     * 实验性功能，将来可能移除的。\n     */\n    private void experiment() {\n        //        ExtRegions.me().add(new MonitorExtRegion());\n    }\n\n    @Override\n    public void setWithNo(int withNo) {\n        this.withNo = withNo;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/BrokerClientApplication.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client;\n\nimport com.alipay.remoting.rpc.RpcConfigs;\nimport com.iohao.game.action.skeleton.core.ActionCommandRegionGlobalCheckKit;\nimport com.iohao.game.action.skeleton.core.ActionCommandRegions;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientBuilder;\nimport lombok.experimental.UtilityClass;\n\n/**\n * BoltBrokerClient 构建与启动\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@UtilityClass\npublic class BrokerClientApplication {\n\n    /**\n     * 构建并启动 BoltBrokerClient\n     *\n     * @param brokerClientStartup brokerClientStartup\n     * @return BoltBrokerClient\n     */\n    public BrokerClient start(AbstractBrokerClientStartup brokerClientStartup) {\n\n        BrokerClientBuilder brokerClientBuilder = brokerClientStartup.initConfig();\n\n        BrokerClient brokerClient = start(brokerClientBuilder);\n\n        brokerClientStartup.startupSuccess(brokerClient);\n\n        IoGameBanner.render();\n\n        return brokerClient;\n    }\n\n    public BrokerClient start(BrokerClientBuilder builder) {\n        // #100\n        System.setProperty(RpcConfigs.DISPATCH_MSG_LIST_IN_DEFAULT_EXECUTOR, \"false\");\n\n        BrokerClient brokerClient = builder.build();\n        brokerClient.init();\n\n        experiment(builder);\n\n        return brokerClient;\n    }\n\n    public BrokerClientBuilder initConfig(AbstractBrokerClientStartup brokerClientStartup) {\n        return brokerClientStartup.initConfig();\n    }\n\n    /**\n     * 实验性功能，将来可能移除的。\n     */\n    private void experiment(BrokerClientBuilder builder) {\n        ActionCommandRegions actionCommandRegions = builder.barSkeleton().getActionCommandRegions();\n        String tag = builder.tag();\n\n        /*\n         * 全局重复路由校验\n         * 原计划是内置到业务框架中，但是突然想起单进程中可以启动多个相同的逻辑服\n         * 所以放到这里比较合适，即使有多个游戏逻辑服，可以用 tag 来区分\n         *\n         * 实际上这个全局重复路由检测是可有可无的，如果遵循 COC ，是可以不需要的。\n         *\n         */\n        ActionCommandRegionGlobalCheckKit.putActionCommandRegions(tag, actionCommandRegions);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/BrokerClientStartup.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.bolt.broker.core.client.*;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.kit.NetworkKit;\n\n/**\n * BoltBrokerClient 的配置\n *\n * @author 渔民小镇\n * @date 2022-04-29\n */\npublic sealed interface BrokerClientStartup permits AbstractBrokerClientStartup {\n    /**\n     * 初始化 业务框架\n     * <pre>\n     *     如果不需要业务框架的逻辑服，使用下面的示例代码\n     *     {@code return BarSkeleton.newBuilder().build();}\n     * </pre>\n     *\n     * @return 业务框架\n     */\n    BarSkeleton createBarSkeleton();\n\n    /**\n     * BoltBrokerClient 构建器\n     *\n     * <pre>\n     *     see {@link BrokerClient#newBuilder()}\n     *\n     *     see {@link AbstractBrokerClientStartup#setBrokerClientBuilder(BrokerClientBuilder)}\n     * </pre>\n     *\n     * @return 构建器\n     */\n    BrokerClientBuilder createBrokerClientBuilder();\n\n    /**\n     * 初始化 远程连接地址 （连接到游戏网关的地址）\n     * <pre>\n     *     地址格式:  ip:port\n     *     如: 127.0.0.1:10200\n     *\n     *     默认方法中提供了本地连接 broker（游戏网关） 的地址\n     *     如果不能满足业务的，可以重写此方法\n     * </pre>\n     *\n     * @return 远程连接地址\n     */\n    default BrokerAddress createBrokerAddress() {\n        // 类似 127.0.0.1 ，但这里是本机的 ip\n        String localIp = NetworkKit.LOCAL_IP;\n        // broker （游戏网关）默认端口\n        int brokerPort = IoGameGlobalConfig.brokerPort;\n        return new BrokerAddress(localIp, brokerPort);\n    }\n\n    /**\n     * 添加连接处理器\n     * <pre>\n     *     see:\n     *     {@link com.alipay.remoting.ConnectionEventType#CLOSE}\n     *     {@link com.alipay.remoting.ConnectionEventType#CONNECT}\n     *\n     *     默认方法中提供了一些比较通用的连接处理器，如果不能满足业务的，可以重写此方法\n     * </pre>\n     *\n     * @param brokerClientBuilder boltBrokerClientBuilder\n     */\n    void connectionEventProcessor(BrokerClientBuilder brokerClientBuilder);\n\n    /**\n     * 注册用户处理器\n     * <pre>\n     *     默认方法中提供了一些比较通用的用户处理器，如果不能满足业务的，可以重写此方法\n     * </pre>\n     *\n     * @param brokerClientBuilder boltBrokerClientBuilder\n     */\n    void registerUserProcessor(BrokerClientBuilder brokerClientBuilder);\n\n    /**\n     * BrokerClient 启动后的钩子方法\n     * <pre>\n     *     如果有需要，可以在这里 保存一下 BrokerClient 的引用\n     *\n     *     框架会在逻辑服启动时，在 {@link BrokerClientHelper} 中保存了一份 BrokerClient 的引用\n     * </pre>\n     *\n     * @param brokerClient BrokerClient\n     */\n    default void startupSuccess(BrokerClient brokerClient) {\n        // 对于 brokerClient 的引用使用，建议用 BrokerClientHolder\n        String id = brokerClient.getId();\n        BrokerClients.put(id, brokerClient);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/RemoteAddress.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client;\n\n/**\n * 远程地址\n *\n * @author 渔民小镇\n * @date 2021-12-17\n */\npublic record RemoteAddress(String ip, int port) {\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 逻辑服，逻辑服通常指的是游戏对外服和游戏逻辑服。\n * <p>\n * 相关文档介绍\n * <pre>\n *     <a href=\"https://iohao.github.io/game/docs/overall/external_intro\">游戏对外服</a>\n *     <a href=\"https://iohao.github.io/game/docs/overall/logic_intro\">游戏逻辑服</a>\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.bolt.broker.client;"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/BoltChannelContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.iohao.game.action.skeleton.core.commumication.ChannelContext;\nimport lombok.NonNull;\n\n/**\n * @author 渔民小镇\n * @date 2022-12-04\n */\npublic record BoltChannelContext(@NonNull AsyncContext asyncContext) implements ChannelContext {\n    @Override\n    public void sendResponse(Object responseObject) {\n        this.asyncContext.sendResponse(responseObject);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/BrokerClientLineKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientAttr;\nimport com.iohao.game.bolt.broker.core.common.processor.listener.BrokerClientListenerRegion;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2024-02-06\n */\n@UtilityClass\npublic class BrokerClientLineKit {\n    public void executeSafe(BrokerClientModuleMessage moduleMessage, Runnable runnable) {\n        String tag = moduleMessage.getTag();\n        int index = Math.abs(tag.hashCode());\n\n        ExecutorRegionKit\n                .getSimpleThreadExecutor(index)\n                .executeTry(runnable);\n    }\n\n    public void onlineProcess(BrokerClientModuleMessage moduleMessage, BrokerClient brokerClient) {\n        String moduleMessageId = moduleMessage.getId();\n\n        // 在集群模式下，可能会触发多次；对于逻辑服的上线处理，默认只处理一次。\n        Set<String> onlineListenerRecordSet = brokerClient.option(BrokerClientAttr.onlineListenerRecordSet);\n        if (onlineListenerRecordSet.contains(moduleMessageId)) {\n            return;\n        }\n\n        onlineListenerRecordSet.add(moduleMessageId);\n\n        Set<String> offlineListenerRecordSet = brokerClient.option(BrokerClientAttr.offlineListenerRecordSet);\n        offlineListenerRecordSet.remove(moduleMessageId);\n\n        // BrokerClientOnlineMessage 是 Broker（游戏网关）发送过来的信息，表示有新的逻辑服上线\n        BrokerClientListenerRegion listenerRegion = brokerClient.getBrokerClientListenerRegion();\n        listenerRegion.forEach(listener -> {\n            switch (moduleMessage.getBrokerClientType()) {\n                case EXTERNAL -> listener.onlineExternal(moduleMessage, brokerClient);\n                case LOGIC -> listener.onlineLogic(moduleMessage, brokerClient);\n            }\n        });\n    }\n\n    public void offlineProcess(BrokerClientModuleMessage moduleMessage, BrokerClient brokerClient) {\n        String moduleMessageId = moduleMessage.getId();\n\n        // 在集群模式下，可能会触发多次；对于逻辑服的下线处理，默认只处理一次。\n        Set<String> offlineListenerRecordSet = brokerClient.option(BrokerClientAttr.offlineListenerRecordSet);\n        if (offlineListenerRecordSet.contains(moduleMessageId)) {\n            return;\n        }\n\n        offlineListenerRecordSet.add(moduleMessageId);\n\n        Set<String> onlineListenerRecordSet = brokerClient.option(BrokerClientAttr.onlineListenerRecordSet);\n        onlineListenerRecordSet.remove(moduleMessageId);\n\n        // BrokerClientOfflineMessage 是 Broker（游戏网关）发送过来的信息，表示有逻辑服下线\n        BrokerClientListenerRegion listenerRegion = brokerClient.getBrokerClientListenerRegion();\n        listenerRegion.forEach(listener -> {\n            switch (moduleMessage.getBrokerClientType()) {\n                case EXTERNAL -> listener.offlineExternal(moduleMessage, brokerClient);\n                case LOGIC -> listener.offlineLogic(moduleMessage, brokerClient);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/BrokerClientOfflineMessageLogicProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientOfflineMessage;\nimport lombok.Setter;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Setter\npublic final class BrokerClientOfflineMessageLogicProcessor extends AsyncUserProcessor<BrokerClientOfflineMessage>\n        implements BrokerClientAware {\n\n    BrokerClient brokerClient;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BrokerClientOfflineMessage message) {\n        // BrokerClientOnlineMessage 是 Broker（游戏网关）发送过来的信息，表示有逻辑服下线\n        BrokerClientModuleMessage moduleMessage = message.getModuleMessage();\n\n        BrokerClientLineKit.executeSafe(moduleMessage, () -> {\n            // offline process\n            BrokerClientLineKit.offlineProcess(moduleMessage, brokerClient);\n        });\n    }\n\n    @Override\n    public String interest() {\n        return BrokerClientOfflineMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/BrokerClientOnlineMessageLogicProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientOnlineMessage;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 逻辑服在线通知\n *\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Slf4j\n@Setter\npublic final class BrokerClientOnlineMessageLogicProcessor extends AsyncUserProcessor<BrokerClientOnlineMessage>\n        implements BrokerClientAware {\n\n    BrokerClient brokerClient;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BrokerClientOnlineMessage message) {\n        // 在线上的其他逻辑服（其他逻辑服指的是除自己外的其他游戏对外服、其他游戏逻辑服）\n        BrokerClientModuleMessage moduleMessage = message.getModuleMessage();\n\n        BrokerClientLineKit.executeSafe(moduleMessage, () -> {\n            // online process\n            BrokerClientLineKit.onlineProcess(moduleMessage, brokerClient);\n        });\n    }\n\n    @Override\n    public String interest() {\n        return BrokerClientOnlineMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/BrokerClusterMessageClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientItem;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientManager;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BrokerClusterMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.ExecutorKit;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * 集群消息请求处理器\n * <pre>\n *     如果有新的 broker （游戏网关）加入集群，逻辑服会通过这个请求处理器来接收消息\n *     逻辑服（对外服和游戏逻辑服）接收到这个消息后，可根据传入的集群消息来建立连接\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@Slf4j(topic = IoGameLogName.ClusterTopic)\npublic class BrokerClusterMessageClientProcessor extends AsyncUserProcessor<BrokerClusterMessage>\n        implements BrokerClientAware {\n    final ExecutorService executorService = ExecutorKit.newSingleThreadExecutor(\"BrokerClusterMessageClientProcessor\");\n\n    @Setter\n    BrokerClient brokerClient;\n\n    @Override\n    public Executor getExecutor() {\n        return executorService;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, BrokerClusterMessage message) {\n        long clusterExecutorIndex = IoGameGlobalConfig.InternalConfig.clusterExecutorIndex;\n\n        ExecutorRegionKit\n                .getSimpleThreadExecutor(clusterExecutorIndex)\n                .executeTry(() -> extracted(message));\n    }\n\n    private void extracted(BrokerClusterMessage message) {\n        List<BrokerMessage> brokerMessageList = message.getBrokerMessageList();\n        if (CollKit.isEmpty(brokerMessageList)) {\n            return;\n        }\n\n        if (IoGameGlobalConfig.isBrokerClusterLog()) {\n            IoGameBanner.printLine();\n            log.info(\"==接收来自网关的集群消息 {} - {} - {}\", brokerMessageList.size(), message.getName(), message);\n        }\n\n        BrokerClientManager brokerClientManager = brokerClient.getBrokerClientManager();\n        Set<String> keySet = brokerClientManager.keySet();\n\n        // 新增网关\n        for (BrokerMessage brokerMessage : brokerMessageList) {\n            String address = brokerMessage.getAddress();\n\n            keySet.remove(address);\n\n            // 如果 BrokerClientManager 有这个集群的地址，说明机器已经存在了\n            if (brokerClientManager.contains(address)) {\n                continue;\n            }\n\n            Map<String, BrokerClientItem> brokerClientItemMap = brokerClientManager.getBrokerClientItemMap();\n            if (brokerClientItemMap.containsKey(address)) {\n                continue;\n            }\n\n            if (IoGameGlobalConfig.isBrokerClusterLog()) {\n                log.info(\"集群有新的机器 address : {}\", address);\n            }\n\n            brokerClientManager.register(address);\n        }\n\n        // 多出来的，要移除；通常是 broker （游戏网关）的机器减少了\n        for (String address : keySet) {\n\n            if (address.contains(\"127.0.0.1\")) {\n                continue;\n            }\n\n            brokerClientManager.remove(address);\n        }\n    }\n\n    @Override\n    public String interest() {\n        return BrokerClusterMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/EventBusMessageClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.SkeletonAttr;\nimport com.iohao.game.action.skeleton.eventbus.EventBus;\nimport com.iohao.game.action.skeleton.eventbus.EventBusMessage;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 分布式事件总线 brokerClient\n *\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@Setter\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class EventBusMessageClientProcessor extends AbstractAsyncUserProcessor<EventBusMessage>\n        implements BrokerClientAware {\n\n    BrokerClient brokerClient;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, final EventBusMessage eventBusMessage) {\n        BarSkeleton barSkeleton = brokerClient.getBarSkeleton();\n        EventBus eventBus = barSkeleton.option(SkeletonAttr.eventBus);\n        eventBus.fireMe(eventBusMessage);\n    }\n\n    @Override\n    public String interest() {\n        return EventBusMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/RequestBrokerClientModuleMessageClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientItemAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientItem;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientManager;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.RequestBrokerClientModuleMessage;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 收到网关请求模块信息\n * <pre>\n *     see ConnectionEventBrokerProcessor.java\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\n@Slf4j\n@Setter\npublic class RequestBrokerClientModuleMessageClientProcessor extends AsyncUserProcessor<RequestBrokerClientModuleMessage>\n        implements BrokerClientItemAware {\n\n    BrokerClientItem brokerClientItem;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestBrokerClientModuleMessage request) {\n\n        if (IoGameGlobalConfig.requestResponseLog) {\n            log.info(\"bizCtx.getRemoteAddress() : {}\", bizCtx.getRemoteAddress());\n        }\n\n        int withNo = request.getWithNo();\n        this.brokerClientItem.setBrokerServerWithNo(withNo);\n        // 客户端服务器注册到游戏网关服\n        this.brokerClientItem.registerToBroker();\n\n        if (IoGameGlobalConfig.requestResponseLog) {\n            BrokerClient brokerClient = brokerClientItem.getBrokerClient();\n            BrokerClientManager brokerClientManager = brokerClient.getBrokerClientManager();\n            log.info(\"brokerClientItems : {}\", brokerClientManager.countActiveItem());\n        }\n\n        IoGameBanner.me().countDown();\n    }\n\n    @Override\n    public String interest() {\n        return RequestBrokerClientModuleMessage.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/RequestMessageClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.commumication.ChannelContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContextKit;\nimport com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientAware;\nimport com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorSelectorAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.common.UserProcessorExecutorSelectorStrategy;\nimport com.iohao.game.bolt.broker.core.common.processor.hook.ClientProcessorHooks;\nimport com.iohao.game.bolt.broker.core.common.processor.hook.RequestMessageClientProcessorHook;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 客户端请求处理器\n * <pre>\n *     通过业务框架把请求派发给指定的业务类来处理\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class RequestMessageClientProcessor extends AbstractAsyncUserProcessor<RequestMessage>\n        implements BrokerClientAware, UserProcessorExecutorSelectorAware {\n    BrokerClient brokerClient;\n    RequestMessageClientProcessorHook requestMessageClientProcessorHook;\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestMessage request) {\n        try {\n            /*\n             * 多次访问的变量，保存到局部变量，可以提升性能。\n             * 把成员变量的访问变为局部变量的访问 。 通过栈帧访问（线程栈），不用每次从堆中得到成员变量\n             *\n             * 因为这段代码访问频繁，才这样做。常规下不需要这么做\n             * 可以参考 HashMap 的 putVal 方法相关\n             */\n            final BrokerClient brokerClient = this.brokerClient;\n            // 得到逻辑服对应的业务框架\n            BarSkeleton barSkeleton = brokerClient.getBarSkeleton();\n            // 业务框架 flow 上下文\n            FlowContext flowContext = barSkeleton\n                    // 业务框架 flow 上下文 工厂\n                    .getFlowContextFactory()\n                    // 创建 flow 上下文\n                    .createFlowContext();\n\n            // 设置请求参数\n            flowContext.setRequest(request);\n            flowContext.setBarSkeleton(barSkeleton);\n\n            // 动态属性添加\n            ChannelContext channelContext = new BoltChannelContext(asyncCtx);\n            flowContext.option(FlowAttr.channelContext, channelContext);\n            flowContext.option(FlowAttr.brokerClientContext, brokerClient);\n            flowContext.option(FlowAttr.logicServerId, brokerClient.getId());\n            flowContext.option(FlowAttr.logicServerTag, brokerClient.getTag());\n            flowContext.option(FlowAttr.aggregationContext, brokerClient.getCommunicationAggregationContext());\n\n            // 设置 flowContext 的一些属性值\n            FlowContextKit.employ(flowContext);\n            // 执行业务框架\n            this.requestMessageClientProcessorHook.processLogic(barSkeleton, flowContext);\n        } catch (Throwable e) {\n            // 这里做个防范 try，正常情况下是不会出现异常的，除非重写了部分方法引发的。(try 并在控制台中打印异常信息)\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public void setBrokerClient(BrokerClient brokerClient) {\n        this.brokerClient = brokerClient;\n\n        ClientProcessorHooks clientProcessorHooks = brokerClient.getClientProcessorHooks();\n        this.requestMessageClientProcessorHook = clientProcessorHooks.getRequestMessageClientProcessorHook();\n    }\n\n    @Override\n    public String interest() {\n        return RequestMessage.class.getName();\n    }\n\n    @Override\n    public void setUserProcessorExecutorSelector(UserProcessorExecutorSelectorStrategy executorSelector) {\n        this.setExecutorSelector(executorSelector);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/connection/CloseConnectEventClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor.connection;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientItemAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientItem;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientManager;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic class CloseConnectEventClientProcessor implements ConnectionEventProcessor, BrokerClientItemAware {\n    private final AtomicBoolean dicConnected = new AtomicBoolean();\n    private final AtomicInteger disConnectTimes = new AtomicInteger();\n    @Setter\n    BrokerClientItem brokerClientItem;\n\n    @Override\n    public void onEvent(String remoteAddress, Connection conn) {\n        long connectIndex = IoGameGlobalConfig.InternalConfig.connectIndex;\n\n        ExecutorRegionKit\n                .getSimpleThreadExecutor(connectIndex)\n                .executeTry(() -> extracted(remoteAddress, conn));\n    }\n\n    private void extracted(String remoteAddress, Connection conn) {\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"网关断开 ConnectionEventType:【{}】 remoteAddress:【{}】，Connection:【{}】\",\n                    ConnectionEventType.CLOSE, remoteAddress, conn\n            );\n        }\n\n        Objects.requireNonNull(conn);\n        dicConnected.set(true);\n        disConnectTimes.incrementAndGet();\n\n        //  这里要断开与 broker （游戏网关）的连接\n        BrokerClient brokerClient = brokerClientItem.getBrokerClient();\n        BrokerClientManager brokerClientManager = brokerClient.getBrokerClientManager();\n\n        //  在集群时，需要移除与当前 broker （游戏网关）连接的 brokerClientItem\n        brokerClientManager.remove(brokerClientItem);\n\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"网关断开 ConnectionEventType:【{}】 remoteAddress:【{}】，网关连接数量:【{}】\",\n                    ConnectionEventType.CLOSE, remoteAddress, brokerClientManager.countActiveItem()\n            );\n        }\n    }\n\n    public boolean isDisConnected() {\n        return this.dicConnected.get();\n    }\n\n    public int getDisConnectTimes() {\n        return this.disConnectTimes.get();\n    }\n\n    public void reset() {\n        this.disConnectTimes.set(0);\n        this.dicConnected.set(false);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/connection/ConnectEventClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor.connection;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.action.skeleton.i18n.Bundle;\nimport com.iohao.game.action.skeleton.i18n.MessageKey;\nimport com.iohao.game.bolt.broker.core.aware.BrokerClientItemAware;\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientItem;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientManager;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.concurrent.executor.ExecutorRegionKit;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.LongAdder;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic class ConnectEventClientProcessor implements ConnectionEventProcessor, BrokerClientItemAware {\n    private final AtomicBoolean connected = new AtomicBoolean();\n    private final AtomicInteger connectTimes = new AtomicInteger();\n    private Connection connection;\n    private String remoteAddress;\n    private final CountDownLatch latch = new CountDownLatch(1);\n    static final LongAdder count = new LongAdder();\n    @Setter\n    BrokerClientItem brokerClientItem;\n\n    @Override\n    public void onEvent(String remoteAddress, Connection conn) {\n        long connectIndex = IoGameGlobalConfig.InternalConfig.connectIndex;\n\n        ExecutorRegionKit\n                .getSimpleThreadExecutor(connectIndex)\n                .executeTry(() -> extracted(remoteAddress, conn));\n    }\n\n    private void extracted(String remoteAddress, Connection conn) {\n        doCheckConnection(conn);\n        this.remoteAddress = remoteAddress;\n        this.connection = conn;\n        connected.set(true);\n        connectTimes.incrementAndGet();\n        latch.countDown();\n\n        // 设置连接\n        brokerClientItem.setConnection(conn);\n        count.increment();\n\n        BrokerClient brokerClient = brokerClientItem.getBrokerClient();\n        BrokerClientManager brokerClientManager = brokerClient.getBrokerClientManager();\n        // 重连\n        brokerClientManager.register(brokerClientItem);\n\n        if (IoGameGlobalConfig.openLog) {\n            String gameBrokerServerConnectionAmount = Bundle.getMessage(MessageKey.gameBrokerServerConnectionAmount);\n\n            log.info(\"ConnectionEventType:【{}】 remoteAddress:【{}】，{}:【{}】，registerActive:【{}】\",\n                    ConnectionEventType.CONNECT,\n                    remoteAddress,\n                    gameBrokerServerConnectionAmount,\n                    brokerClientManager.countItem(),\n                    brokerClientManager.countActiveItem()\n            );\n        }\n    }\n\n    private void doCheckConnection(Connection conn) {\n        Objects.requireNonNull(conn);\n        Objects.requireNonNull(conn.getPoolKeys());\n        Objects.requireNonNull(conn.getChannel());\n        Objects.requireNonNull(conn.getUrl());\n        Objects.requireNonNull(conn.getChannel().attr(Connection.CONNECTION).get());\n    }\n\n    public boolean isConnected() throws InterruptedException {\n        latch.await();\n        return this.connected.get();\n    }\n\n    public int getConnectTimes() throws InterruptedException {\n        latch.await();\n        return this.connectTimes.get();\n    }\n\n    public Connection getConnection() throws InterruptedException {\n        latch.await();\n        return this.connection;\n    }\n\n    public String getRemoteAddress() throws InterruptedException {\n        latch.await();\n        return this.remoteAddress;\n    }\n\n    public void reset() {\n        this.connectTimes.set(0);\n        this.connected.set(false);\n        this.connection = null;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/connection/ConnectFailedEventClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor.connection;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-16\n */\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic class ConnectFailedEventClientProcessor implements ConnectionEventProcessor {\n    @Override\n    public void onEvent(String remoteAddress, Connection connection) {\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"ConnectionEventType:【{}】 remoteAddress:【{}】，Connection:【{}】\",\n                    ConnectionEventType.CONNECT_FAILED, remoteAddress, connection\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/connection/ExceptionConnectEventClientProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.processor.connection;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-16\n */\n@Slf4j(topic = IoGameLogName.ConnectionTopic)\npublic class ExceptionConnectEventClientProcessor implements ConnectionEventProcessor {\n    @Override\n    public void onEvent(String remoteAddress, Connection connection) {\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"ConnectionEventType:【{}】 remoteAddress:【{}】，Connection:【{}】\",\n                    ConnectionEventType.EXCEPTION, remoteAddress, connection\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/connection/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 逻辑服 - ConnectionEventProcessor 是逻辑服连接相关的消息处理器\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.bolt.broker.client.processor.connection;"
  },
  {
    "path": "net-bolt/bolt-client/src/main/java/com/iohao/game/bolt/broker/client/processor/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 逻辑服 - processor 用于接收 Broker（游戏网关）的消息处理器。\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.bolt.broker.client.processor;"
  },
  {
    "path": "net-bolt/bolt-core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>bolt-core</artifactId>\n    <name>bolt-core for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>common-core</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/com.alipay.sofa/bolt -->\n        <dependency>\n            <groupId>com.alipay.sofa</groupId>\n            <artifactId>bolt</artifactId>\n            <version>${bolt.version}</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.slf4j</groupId>\n                    <artifactId>slf4j-api</artifactId>\n                </exclusion>\n                <exclusion>\n                    <groupId>io.netty</groupId>\n                    <artifactId>netty-all</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/com.caucho/hessian -->\n        <dependency>\n            <groupId>com.caucho</groupId>\n            <artifactId>hessian</artifactId>\n            <version>${hessian.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-all</artifactId>\n            <version>${netty.version}</version>\n        </dependency>\n\n    </dependencies>\n\n</project>"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/alipay/sofa/common/log/factory/LoggerSpaceFactory4LogbackBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.alipay.sofa.common.log.factory;\n\nimport ch.qos.logback.classic.LoggerContext;\nimport ch.qos.logback.classic.joran.JoranConfigurator;\nimport ch.qos.logback.classic.util.ContextInitializer;\nimport ch.qos.logback.classic.util.LogbackMDCAdapter;\nimport ch.qos.logback.core.joran.spi.JoranException;\nimport com.alipay.sofa.common.log.SpaceInfo;\nimport com.alipay.sofa.common.log.adapter.level.AdapterLevel;\nimport com.iohao.game.common.kit.exception.CommonIllegalArgumentException;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport org.slf4j.Logger;\n\nimport java.net.URL;\n\n/**\n * 兼容 slf4j 2.0.x、logback 1.4.x ...等系列。\n * <p>\n * 原因：LoggerSpaceManager 、MultiAppLoggerSpaceManager 没有提供扩展点。\n * <p>\n * 这里使用 class 覆盖的方式做兼容支持。\n *\n * @author 渔民小镇\n * @date 2024-01-07\n */\npublic class LoggerSpaceFactory4LogbackBuilder extends AbstractLoggerSpaceFactoryBuilder {\n    public LoggerSpaceFactory4LogbackBuilder(SpaceInfo spaceInfo) {\n        super(spaceInfo);\n    }\n\n    @Override\n    public AbstractLoggerSpaceFactory doBuild(String spaceName, ClassLoader spaceClassloader, URL url) {\n\n        final LoggerContext loggerContext = new LoggerContext();\n\n        for (var entry : getProperties().entrySet()) {\n            loggerContext.putProperty((String) entry.getKey(), (String) entry.getValue());\n        }\n\n        if (url != null) {\n            try {\n                loggerContext.setMDCAdapter(new LogbackMDCAdapter());\n                new ContextInitializer(loggerContext);\n\n                var configurator = new JoranConfigurator();\n                configurator.setContext(loggerContext);\n                configurator.doConfigure(url);\n            } catch (JoranException e) {\n                ThrowKit.ofIllegalArgumentException(\"Logback loggerSpaceFactory build error\", e);\n            }\n        }\n\n        return new AbstractLoggerSpaceFactory(getLoggingToolName()) {\n            @Override\n            public Logger setLevel(String loggerName, AdapterLevel adapterLevel) {\n                var logbackLogger = (ch.qos.logback.classic.Logger) this.getLogger(loggerName);\n\n                ch.qos.logback.classic.Level logbackLevel = this.toLogbackLevel(adapterLevel);\n                logbackLogger.setLevel(logbackLevel);\n\n                return logbackLogger;\n            }\n\n            @Override\n            public Logger getLogger(String name) {\n                return loggerContext.getLogger(name);\n            }\n\n            private ch.qos.logback.classic.Level toLogbackLevel(AdapterLevel adapterLevel) {\n                if (adapterLevel == null) {\n                    throw new CommonIllegalArgumentException(\"AdapterLevel is NULL when adapter to logback.\");\n                }\n\n                return switch (adapterLevel) {\n                    case TRACE -> ch.qos.logback.classic.Level.TRACE;\n                    case DEBUG -> ch.qos.logback.classic.Level.DEBUG;\n                    case INFO -> ch.qos.logback.classic.Level.INFO;\n                    case WARN -> ch.qos.logback.classic.Level.WARN;\n                    case ERROR -> ch.qos.logback.classic.Level.ERROR;\n                };\n            }\n        };\n    }\n\n    @Override\n    protected String getLoggingToolName() {\n        return \"logback\";\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/client/kit/ExternalCommunicationKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.kit;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalItemMessage;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientHelper;\nimport com.iohao.game.common.kit.exception.CommonRuntimeException;\nimport com.iohao.game.core.common.client.Attachment;\nimport com.iohao.game.core.common.client.ExternalBizCodeCont;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Optional;\n\n/**\n * 这个工具只能在游戏逻辑服中使用\n *\n * @author 渔民小镇\n * @date 2022-07-27\n */\n@UtilityClass\npublic class ExternalCommunicationKit {\n    /**\n     * 玩家是否在线\n     *\n     * @param userId userId\n     * @return true 玩家在线\n     */\n    public boolean existUser(long userId) {\n        RequestCollectExternalMessage request = new RequestCollectExternalMessage()\n                // 根据业务码，调用游戏对外服与业务码对应的业务实现类 （ExistUserExternalBizRegion）\n                .setBizCode(ExternalBizCodeCont.existUser)\n                .setUserId(userId);\n\n        return BrokerClientHelper\n                // 【游戏逻辑服】与【游戏对外服】通讯上下文\n                .getInvokeExternalModuleContext()\n                .invokeExternalModuleCollectMessage(request)\n                // 只要有一条数据存在，就表示正确的\n                .anySuccess();\n    }\n\n    /**\n     * 强制指定玩家下线，让玩家与游戏对外服断开连接\n     *\n     * @param userId 需要强制下线的 userId\n     */\n    public void forcedOffline(long userId) {\n        RequestCollectExternalMessage request = new RequestCollectExternalMessage()\n                // 根据业务码，调用游戏对外服与业务码对应的业务实现类 （ForcedOfflineExternalBizRegion）\n                .setBizCode(ExternalBizCodeCont.forcedOffline)\n                .setUserId(userId);\n\n        /*\n         * 强制玩家下线\n         * 实现类 ForcedOfflineExternalBizRegion\n         * 因为不需要任何的返回值，所以我们只需要调用一下方法就好了\n         */\n        BrokerClientHelper\n                // 【游戏逻辑服】与【游戏对外服】通讯上下文\n                .getInvokeExternalModuleContext()\n                .invokeExternalModuleCollectMessage(request);\n    }\n\n    /**\n     * 设置元信息到游戏对外服\n     * <pre>\n     *     之后所有 action 的 FlowContext 中会携带上这个元信息对象，\n     *     不建议在元信息保存过多的信息，因为会每次传递。\n     * </pre>\n     *\n     * @param attachment  元信息\n     * @param flowContext flowContext\n     */\n    public void setAttachment(Attachment attachment, FlowContext flowContext) {\n        flowContext.updateAttachment(attachment);\n    }\n\n    /**\n     * 给请求添加一些 user 自身所具备的数据，这些数据来自于用户所在游戏对外服\n     * <pre>\n     *     将用户元信息、所绑定的游戏逻辑服设置到 RequestMessage headMetadata 中。\n     *\n     *     注意事项：只有玩家在线才能从其对应的游戏对外服中获取数据。\n     * </pre>\n     *\n     * @param requestMessage 请求（通常是模拟的用户请求）\n     * @return 用户（玩家）所在游戏对外服中的 HeadMetadata 数据，headMetadataOptional 中还包括了一些其他的信息，开发者如果有需要的可从中获取。\n     */\n    public Optional<HeadMetadata> employHeadMetadata(RequestMessage requestMessage) {\n\n        long userId = Optional.ofNullable(requestMessage)\n                .map(RequestMessage::getHeadMetadata)\n                .map(HeadMetadata::getUserId).orElse(0L);\n\n        if (userId <= 0) {\n            throw new CommonRuntimeException(\"userId <= 0\");\n        }\n\n        RequestCollectExternalMessage request = new RequestCollectExternalMessage()\n                // 根据业务码，调用游戏对外服与业务码对应的业务实现类 （UserHeadMetadataExternalBizRegion）\n                .setBizCode(ExternalBizCodeCont.userHeadMetadata)\n                .setUserId(userId)\n                .setData(requestMessage.getHeadMetadata())\n                .setSourceClientId(requestMessage.getHeadMetadata().getSourceClientId());\n\n        Optional<HeadMetadata> headMetadataOptional = BrokerClientHelper\n                // 【游戏逻辑服】与【游戏对外服】通讯上下文\n                .getInvokeExternalModuleContext()\n                .invokeExternalModuleCollectMessage(request)\n                .optionalAnySuccess()\n                .map(ResponseCollectExternalItemMessage::getData);\n\n        headMetadataOptional.ifPresent(externalHeadMetadata -> {\n            // 将用户元信息、所绑定的游戏逻辑服设置到 RequestMessage headMetadata 中\n            HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n            headMetadata.setBindingLogicServerIds(externalHeadMetadata.getBindingLogicServerIds());\n            headMetadata.setAttachmentData(externalHeadMetadata.getAttachmentData());\n        });\n\n        // headMetadataOptional 中还包括了一些其他的信息，如有需要的可从中获取\n        return headMetadataOptional;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/client/kit/UserIdSettingKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.client.kit;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * UserIdSettingKit\n *\n * @author 渔民小镇\n * @date 2022-01-19\n */\n@Slf4j\n@UtilityClass\npublic class UserIdSettingKit {\n    /**\n     * 设置用户的 userId\n     * <pre>\n     *     玩家真正的登录，只有设置了用户的 id， 才算是验证通过\n     * </pre>\n     *\n     * @param flowContext 业务框架 flow上下文\n     * @param userId      一般从数据库中获取\n     * @return true 变更成功\n     * @deprecated see {@link FlowContext#bindingUserId(long)}\n     */\n    @Deprecated\n    public boolean settingUserId(FlowContext flowContext, long userId) {\n        var result = flowContext.bindingUserIdAndGetResult(userId);\n\n        if (!result.success()) {\n            Exception exception = result.exception();\n            log.error(exception.getMessage(), exception);\n        }\n\n        return result.success();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/client/kit/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 逻辑服 - 工具包\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.bolt.broker.client.kit;"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/BoltConnection.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-14\n */\npublic interface BoltConnection {\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/GroupWith.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core;\n\n/**\n * @author 渔民小镇\n * @date 2023-06-09\n */\npublic interface GroupWith {\n    void setWithNo(int withNo);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/AwareInject.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\n/**\n * aware 注入接口\n *\n * @author 渔民小镇\n * @date 2023-02-21\n */\npublic interface AwareInject {\n    /**\n     * 附加能力\n     *\n     * @param o o\n     */\n    void aware(Object o);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/AwareKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\nimport java.util.concurrent.Executor;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n * @since 21.15\n */\n@UtilityClass\npublic class AwareKit {\n    public void aware(Object obj) {\n        // 处理 UserProcessor 所有请求\n        if (obj instanceof UserProcessorExecutorAware aware && Objects.isNull(aware.getUserProcessorExecutor())) {\n            // 如果开发者没有自定义 Executor，则使用框架提供的 Executor 策略\n            Executor executor = IoGameGlobalConfig.getExecutor(aware);\n            aware.setUserProcessorExecutor(executor);\n        }\n\n        // 处理 UserProcessor 业务数据的请求\n        if (obj instanceof UserProcessorExecutorSelectorAware aware) {\n            var executorSelector = IoGameGlobalConfig.getExecutorSelector();\n            aware.setUserProcessorExecutorSelector(executorSelector);\n        }\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/BrokerClientAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\n\n/**\n * BoltBrokerClient aware\n * <pre>\n *     设置与 broker（游戏网关）的 Client\n *\n *     只要 bolt 处理器（逻辑处理器和连接器）实现了该接口，框架会调用 setBrokerClient 方法并赋值\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\npublic interface BrokerClientAware {\n\n    /**\n     * set BoltBrokerClient\n     *\n     * @param brokerClient BoltBrokerClient\n     */\n    void setBrokerClient(BrokerClient brokerClient);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/BrokerClientItemAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClientItem;\n\n/**\n * 只要 bolt 处理器（逻辑处理器和连接器）实现了该接口，框架会调用 setBrokerClientItem 方法并赋值\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\npublic interface BrokerClientItemAware {\n    /**\n     * 设置 BoltClient\n     *\n     * @param brokerClientItem BoltClient\n     */\n    void setBrokerClientItem(BrokerClientItem brokerClientItem);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/CmdRegionsAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.iohao.game.core.common.cmd.CmdRegions;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-01\n */\npublic interface CmdRegionsAware {\n    void setCmdRegions(CmdRegions cmdRegions);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/PulseConsumerAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.iohao.game.action.skeleton.pulse.core.consumer.PulseConsumers;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-21\n */\npublic interface PulseConsumerAware {\n    /**\n     * set PulseConsumers\n     *\n     * @param pulseConsumers 脉冲消费器\n     */\n    void setPulseConsumers(PulseConsumers pulseConsumers);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/PulseProducerAware.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.iohao.game.action.skeleton.pulse.core.producer.PulseProducers;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-22\n */\npublic interface PulseProducerAware {\n    /**\n     * set PulseProducers\n     *\n     * @param pulseProducers 脉冲生产器\n     */\n    void setPulseProducers(PulseProducers pulseProducers);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/UserProcessorExecutorAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport java.util.concurrent.Executor;\n\n/**\n * UserProcessorExecutorAware\n * <pre>\n *\n *     只要 UserProcessor 实现了该接口，框架会调用 setProcessorExecutor 方法并赋值\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-11-10\n */\npublic interface UserProcessorExecutorAware extends UserProcessorInNettyThreadAware {\n    /**\n     * set UserProcessor Executor\n     *\n     * @param executor Executor\n     */\n    void setUserProcessorExecutor(Executor executor);\n\n    /**\n     * get UserProcessor Executor\n     *\n     * @return Executor\n     */\n    Executor getUserProcessorExecutor();\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/UserProcessorExecutorSelectorAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.iohao.game.bolt.broker.core.common.UserProcessorExecutorSelectorStrategy;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n * @since 21.15\n */\npublic interface UserProcessorExecutorSelectorAware {\n    void setUserProcessorExecutorSelector(UserProcessorExecutorSelectorStrategy executorSelector);\n}"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/aware/UserProcessorInNettyThreadAware.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.aware;\n\nimport com.alipay.remoting.RemotingContext;\nimport com.alipay.remoting.rpc.RpcHandler;\nimport com.alipay.remoting.rpc.protocol.*;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.kit.ExecutorSelectKit;\n\nimport java.util.concurrent.ExecutorService;\n\n/**\n * 在 netty 线程中执行任务 aware\n * <pre>\n *     默认会在执行 netty handler 的线程中执行任务，而不使用 bolt 的线程执行器；\n *     因为最终会将任务将由 ioGame 默认的执行器策略中，可减少上下文的切换消耗。\n *\n *     相关源码可阅读 DefaultRequestMessageClientProcessorHook\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-20\n * @see ExecutorSelectKit#processLogic(BarSkeleton, FlowContext)\n */\npublic interface UserProcessorInNettyThreadAware {\n    /**\n     * 消费业务请求的线程\n     * <pre>\n     *     默认使用执行 netty handler 的线程中执行业务\n     *\n     *     相关源码\n     *     {@link RpcHandler}\n     *     {@link RpcRequestProcessor#process(RemotingContext, RpcRequestCommand, ExecutorService)}\n     *\n     * </pre>\n     *\n     * @param inNettyThread true 表示在 netty 中执行业务\n     */\n    void setInNettyThread(boolean inNettyThread);\n\n    /**\n     * 是否在执行 netty handler 的线程中执行业务\n     *\n     * @return true 表示在执行 netty handler 的线程中执行业务\n     */\n    boolean inNettyThreadExecute();\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/Broadcast.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.alipay.remoting.exception.RemotingException;\nimport com.iohao.game.action.skeleton.core.DevConfig;\nimport com.iohao.game.action.skeleton.core.IoGameCommonCoreConfig;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.bolt.broker.core.message.BroadcastMessage;\nimport com.iohao.game.bolt.broker.core.message.BroadcastOrderMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Collection;\n\n/**\n * 广播相关操作\n *\n * @author 渔民小镇\n * @date 2022-01-29\n */\n@Slf4j(topic = IoGameLogName.MsgTransferTopic)\npublic record Broadcast(BrokerClientItem brokerClientItem) {\n    /**\n     * 广播消息给单个用户\n     *\n     * @param responseMessage 消息\n     * @param userId          userId\n     */\n    public void broadcast(ResponseMessage responseMessage, long userId) {\n\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        headMetadata.setUserId(userId);\n\n        // 广播消息\n        BroadcastMessage broadcastMessage = new BroadcastMessage()\n                .setResponseMessage(responseMessage);\n\n        // 发送广播消息\n        this.internalBroadcast(broadcastMessage);\n    }\n\n    /**\n     * 广播消息给指定用户列表\n     *\n     * @param responseMessage 消息\n     * @param userIdList      指定用户列表\n     */\n    public void broadcast(ResponseMessage responseMessage, Collection<Long> userIdList) {\n        // 广播消息\n        BroadcastMessage broadcastMessage = new BroadcastMessage()\n                .setResponseMessage(responseMessage)\n                .setUserIdList(userIdList);\n\n        // 发送广播消息\n        this.internalBroadcast(broadcastMessage);\n    }\n\n    /**\n     * 广播给全部用户\n     *\n     * @param responseMessage responseMessage\n     */\n    public void broadcast(ResponseMessage responseMessage) {\n        // 广播消息\n        BroadcastMessage broadcastMessage = new BroadcastMessage()\n                .setResponseMessage(responseMessage)\n                .setBroadcastAll(true);\n\n        // 发送广播消息\n        this.internalBroadcast(broadcastMessage);\n    }\n\n    void internalBroadcast(BroadcastMessage broadcastMessage) {\n        try {\n            this.brokerClientItem.oneway(broadcastMessage);\n        } catch (RemotingException e) {\n            log.error(e.getMessage(), e);\n        }\n\n        if (IoGameCommonCoreConfig.broadcastLog) {\n            // 打印广播日志\n            BroadcastDebug.print(broadcastMessage);\n        }\n    }\n\n    public void broadcastOrder(ResponseMessage responseMessage, Collection<Long> userIdList) {\n        // 广播消息\n        BroadcastOrderMessage orderMessage = new BroadcastOrderMessage();\n\n        orderMessage.setResponseMessage(responseMessage)\n                .setUserIdList(userIdList);\n\n        // 发送广播消息\n        this.internalBroadcast(orderMessage);\n    }\n\n    public void broadcastOrder(ResponseMessage responseMessage, long userId) {\n        HeadMetadata headMetadata = responseMessage.getHeadMetadata();\n        headMetadata.setUserId(userId);\n\n        // 广播消息\n        BroadcastOrderMessage orderMessage = new BroadcastOrderMessage();\n\n        orderMessage.setResponseMessage(responseMessage);\n\n        // 发送广播消息\n        this.internalBroadcast(orderMessage);\n    }\n\n    public void broadcastOrder(ResponseMessage responseMessage) {\n        BroadcastOrderMessage orderMessage = new BroadcastOrderMessage();\n\n        orderMessage.setResponseMessage(responseMessage)\n                .setBroadcastAll(true);\n\n        // 发送广播消息\n        this.internalBroadcast(orderMessage);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BroadcastDebug.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.core.commumication.BroadcastContext;\nimport com.iohao.game.action.skeleton.kit.RangeBroadcast;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.core.message.BroadcastMessage;\nimport com.iohao.game.bolt.broker.core.message.BroadcastOrderMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.ArrayKit;\nimport com.iohao.game.common.kit.CollKit;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-19\n */\n@UtilityClass\n@Slf4j(topic = IoGameLogName.CommonStdout)\nclass BroadcastDebug {\n    final Map<String, Class<?>> classMap = new NonBlockingHashMap<>();\n\n    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\n    void print(BroadcastMessage broadcastMessage) {\n        RuntimeException ex = new RuntimeException();\n        StackTraceElement[] traces = ex.getStackTrace();\n        int index = lookBusinessCodeInTrace(traces);\n        StackTraceElement traceElement = traces[index];\n\n        // 接收广播的用户\n        String userIds = getUserIds(broadcastMessage);\n        ResponseMessage responseMessage = broadcastMessage.getResponseMessage();\n        Object returnData = getReturnData(responseMessage);\n\n        Map<String, Object> paramMap = new HashMap<>();\n        paramMap.put(\"returnData\", returnData);\n        paramMap.put(\"userIds\", userIds);\n        paramMap.put(\"className\", traceElement.getFileName());\n        paramMap.put(\"lineNumber\", traceElement.getLineNumber());\n        paramMap.put(\"cmdInfo\", responseMessage.getHeadMetadata().getCmdInfo());\n        paramMap.put(\"time\", dateTimeFormatter.format(LocalDateTime.now()));\n\n        String title = broadcastMessage instanceof BroadcastOrderMessage ? \"严格顺序广播\" : \"广播\";\n        paramMap.put(\"title\", title);\n\n        String template = \"\"\"\n                ┏━━━━━ {title}. [({className}:{lineNumber})] ━━━ {cmdInfo}\n                ┣ userId: {userIds}\n                ┣ 广播数据: {returnData}\n                ┣ 广播时间: {time}\n                ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n                \"\"\";\n\n        String message = StrKit.format(template, paramMap);\n        log.info(\"\\n{}\", message);\n    }\n\n    private Object getReturnData(ResponseMessage responseMessage) {\n\n        byte[] data = responseMessage.getData();\n        String dataClass = responseMessage.getDataClass();\n\n        if (ArrayKit.isEmpty(data) || Objects.isNull(dataClass)) {\n            return \"null or []\";\n        }\n\n        Class<?> aClass = null;\n\n        try {\n            aClass = getDataClass(dataClass);\n            if (Objects.isNull(aClass)) {\n                return \"null\";\n            }\n        } catch (ClassNotFoundException e) {\n            log.error(e.getMessage(), e);\n        }\n\n        return DataCodecKit.decode(responseMessage.getData(), aClass);\n    }\n\n    private Class<?> getDataClass(String dataClass) throws ClassNotFoundException {\n        Class<?> aClass = classMap.get(dataClass);\n\n        // 无锁化\n        if (aClass == null) {\n            Class<?> newValue = Class.forName(dataClass);\n            return MoreKit.putIfAbsent(classMap, dataClass, newValue);\n        }\n\n        return aClass;\n    }\n\n    private String getUserIds(BroadcastMessage broadcastMessage) {\n\n        if (broadcastMessage.isBroadcastAll()) {\n            return \"全服广播\";\n        }\n\n        var userIdList = broadcastMessage.getUserIdList();\n        if (CollKit.notEmpty(userIdList)) {\n            return userIdList.toString();\n        }\n\n        long userId = broadcastMessage.getResponseMessage().getHeadMetadata().getUserId();\n\n        if (userId != 0) {\n            return String.valueOf(userId);\n        }\n\n        return \"\";\n    }\n\n    private int lookBusinessCodeInTrace(StackTraceElement[] traces) {\n        for (int index = traces.length - 1; index >= 0; index--) {\n            String name = traces[index].getClassName();\n            // 广播对象\n            if (BroadcastContext.class.getName().equals(name)) {\n                return index + 1;\n            }\n\n            // 范围广播\n            if (RangeBroadcast.class.getName().equals(name)) {\n                return index + 1;\n            }\n\n            if (\"com.iohao.game.action.skeleton.core.flow.SimpleCommunicationBroadcast\".equals(name)) {\n                return index + 1;\n            }\n        }\n\n        return 2;\n    }\n\n    private void extracted(StackTraceElement traceElement) {\n        String className = traceElement.getClassName();\n        String methodName = traceElement.getMethodName();\n        int line = traceElement.getLineNumber();\n\n        String format = \"className:%s - methodName:%s - line:%d\";\n        String msg = String.format(format, className, methodName, line);\n        IoGameBanner.printlnMsg(msg);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerAddress.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\n/**\n * 连接 broker （游戏网关） 的地址\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\npublic record BrokerAddress(String host, int port) {\n    public String getAddress() {\n        return host + \":\" + port;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClient.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.alipay.remoting.config.Configs;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.alipay.remoting.rpc.protocol.UserProcessor;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.SkeletonAttr;\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;\nimport com.iohao.game.action.skeleton.protocol.processor.SimpleServerInfo;\nimport com.iohao.game.bolt.broker.core.GroupWith;\nimport com.iohao.game.bolt.broker.core.aware.AwareInject;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.common.processor.hook.ClientProcessorHooks;\nimport com.iohao.game.bolt.broker.core.common.processor.listener.BrokerClientListenerRegion;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.common.kit.attr.AttrOptionDynamic;\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Supplier;\n\n/**\n * BrokerClient 是与 broker（游戏网关）通信的 client\n * <p>\n * 对外服、逻辑服都是 broker 的 client\n * <pre>\n *     see {@link BrokerClientBuilder#build()}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n * @see BrokerClientHelper\n * @see BrokerClientAttr\n */\n@Slf4j\n@Getter\n@Accessors(chain = true)\n@SuppressWarnings(\"unchecked\")\n@Setter(value = AccessLevel.PROTECTED)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClient implements BrokerClientContext, GroupWith, AttrOptionDynamic {\n    final AttrOptions options = new AttrOptions();\n\n    /** 服务器唯一标识 */\n    String id;\n    /**\n     * 逻辑服标签 （tag 相当于归类）\n     * <pre>\n     *     用于逻辑服的归类\n     *     假设逻辑服： 战斗逻辑服 启动了两台或以上，为了得到启动连接的逻辑服，我们可以通过 tag 在后台查找\n     *     相同的逻辑服一定要用相同的 tag\n     *\n     *     注意，如果没设置这个值，会使用 this.appName 的值\n     * </pre>\n     */\n    String tag;\n    /** 模块名 */\n    String appName;\n\n    /** 业务框架 */\n    BarSkeleton barSkeleton;\n\n    /** 连接 broker （游戏网关） 的地址 */\n    BrokerAddress brokerAddress;\n    /** 逻辑服类型 */\n    BrokerClientType brokerClientType = BrokerClientType.LOGIC;\n    /** 模块信息 （子游戏服的信息、逻辑服） */\n    BrokerClientModuleMessage brokerClientModuleMessage;\n\n    /** 消息发送超时时间 */\n    int timeoutMillis = IoGameGlobalConfig.timeoutMillis;\n\n    BrokerClientManager brokerClientManager;\n\n    /** 连接事件 */\n    Map<ConnectionEventType, Supplier<ConnectionEventProcessor>> connectionEventProcessorMap;\n    /** 用户处理器 */\n    List<Supplier<UserProcessor<?>>> processorList;\n\n    AtomicBoolean initAtomic = new AtomicBoolean(false);\n\n    /** bolt 业务处理器的钩子管理器 */\n    ClientProcessorHooks clientProcessorHooks;\n    /** 监听 */\n    BrokerClientListenerRegion brokerClientListenerRegion;\n\n    /** 简单的服务器信息 */\n    SimpleServerInfo simpleServerInfo;\n\n    /** aware 注入扩展 */\n    AwareInject awareInject;\n    /** 逻辑服状态 */\n    int status;\n    int withNo;\n\n    BrokerClient() {\n        // 开启 bolt 重连, 通过系统属性来开和关，如果一个进程有多个 RpcClient，则同时生效\n        System.setProperty(Configs.CONN_MONITOR_SWITCH, \"true\");\n        System.setProperty(Configs.CONN_RECONNECT_SWITCH, \"true\");\n    }\n\n    public static BrokerClientBuilder newBuilder() {\n        return new BrokerClientBuilder();\n    }\n\n    public void init() {\n        if (initAtomic.get()) {\n            return;\n        }\n\n        if (initAtomic.compareAndSet(false, true)) {\n            // 在业务框架中保存一份与之相关的 BrokerClient 引用\n            this.barSkeleton.option(SkeletonAttr.brokerClientContext, this);\n            BrokerClientModuleMessage moduleMessage = this.getBrokerClientModuleMessage();\n            int idHash = moduleMessage.getIdHash();\n            this.barSkeleton.option(SkeletonAttr.logicServerIdHash, idHash);\n\n            // 启动 runner 机制\n            this.barSkeleton.getRunners().onStart();\n\n            // brokerClientListener 监听\n            BrokerClient client = this;\n            this.brokerClientListenerRegion.forEach(listener -> {\n                //  向 Broker（游戏网关）发起连接前的监听回调\n                listener.registerBefore(moduleMessage, client);\n            });\n\n            // 初始化一些信息，并将逻辑服信息发送给 Broker（游戏网关）\n            this.initBrokerClientManager();\n        }\n    }\n\n    private BrokerClientItem next() {\n        return this.brokerClientManager.next();\n    }\n\n    @Override\n    public CommunicationAggregationContext getCommunicationAggregationContext() {\n        return this.brokerClientManager.next();\n    }\n\n    public <T> T invokeSync(final Object request, final int timeoutMillis) throws RemotingException, InterruptedException {\n        BrokerClientItem nextClient = next();\n        return (T) nextClient.invokeSync(request, timeoutMillis);\n    }\n\n    @Override\n    public <T> T invokeSync(final Object request) throws RemotingException, InterruptedException {\n        return invokeSync(request, timeoutMillis);\n    }\n\n    @Override\n    public void oneway(final Object request) throws Exception {\n        BrokerClientItem nextClient = next();\n        nextClient.oneway(request);\n    }\n\n    @Override\n    public SimpleServerInfo getSimpleServerInfo() {\n        return this.simpleServerInfo;\n    }\n\n    void invokeWithCallback(Object request) throws RemotingException {\n        BrokerClientItem nextClient = next();\n        nextClient.invokeWithCallback(request);\n    }\n\n    @Override\n    public void sendResponse(Object responseObject) {\n        try {\n            BrokerClientItem nextClient = next();\n            nextClient.oneway(responseObject);\n        } catch (RemotingException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    private void initBrokerClientManager() {\n        if (Objects.isNull(this.brokerClientManager)) {\n            this.brokerClientManager = new BrokerClientManager();\n        }\n\n        this.brokerClientManager\n                .setBrokerAddress(this.brokerAddress)\n                .setConnectionEventProcessorMap(this.connectionEventProcessorMap)\n                .setProcessorList(this.processorList)\n                .setBarSkeleton(this.barSkeleton)\n                .setTimeoutMillis(this.timeoutMillis)\n                .setBrokerClient(this)\n        ;\n\n        this.brokerClientManager.init();\n    }\n\n    @Override\n    public void setWithNo(int withNo) {\n        this.withNo = withNo;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClientAttr.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.iohao.game.common.kit.attr.AttrOption;\n\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2024-02-06\n */\npublic interface BrokerClientAttr {\n    AttrOption<Set<String>> onlineListenerRecordSet = AttrOption.valueOf(\"onlineListenerRecordSet\");\n    AttrOption<Set<String>> offlineListenerRecordSet = AttrOption.valueOf(\"offlineListenerRecordSet\");\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClientBuilder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.alipay.remoting.rpc.protocol.UserProcessor;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.protocol.processor.SimpleServerInfo;\nimport com.iohao.game.bolt.broker.core.aware.AwareInject;\nimport com.iohao.game.bolt.broker.core.client.config.BrokerClientStatusConfig;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.common.processor.hook.ClientProcessorHooks;\nimport com.iohao.game.bolt.broker.core.common.processor.listener.BrokerClientListener;\nimport com.iohao.game.bolt.broker.core.common.processor.listener.BrokerClientListenerRegion;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.common.kit.HashKit;\nimport com.iohao.game.common.kit.NetworkKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.*;\nimport java.util.function.Supplier;\n\n/**\n * Bolt Broker Client （逻辑服）构建器\n * <pre>\n *     see {@link BrokerClient#newBuilder()} 创建构建器\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Setter\n@Getter\n@Accessors(fluent = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClientBuilder {\n    /** 模拟的同进程 id */\n    static final String ioGamePidRandom = UUID.randomUUID().toString();\n    /** 连接处理器 */\n    final Map<ConnectionEventType, Supplier<ConnectionEventProcessor>> connectionEventProcessorMap = new NonBlockingHashMap<>();\n    /** 用户处理器 */\n    final List<Supplier<UserProcessor<?>>> processorList = new ArrayList<>();\n    /** 需要移除的用户处理器 */\n    @Getter(value = AccessLevel.PRIVATE)\n    final List<Class<?>> removeProcessorList = new ArrayList<>();\n    final BrokerClientListenerRegion brokerClientListenerRegion = new BrokerClientListenerRegion();\n    /**\n     * 服务器唯一标识\n     * <pre>\n     *     如果没设置，会随机分配一个\n     *\n     *     逻辑服的模块id，标记不同的逻辑服模块。\n     *     开发者随意定义，只要确保每个逻辑服的模块 id 不相同就可以\n     * </pre>\n     */\n    String id;\n    /**\n     * 逻辑服标签 （tag 相当于归类）\n     * <pre>\n     *     用于逻辑服的归类\n     *     假设逻辑服： 战斗逻辑服 启动了两台或以上，为了得到启动连接的逻辑服，我们可以通过 tag 在后台查找\n     *     相同的逻辑服一定要用相同的 tag\n     *\n     *      注意，如果没设置这个值，会使用 this.appName 的值\n     * </pre>\n     */\n    String tag;\n    /**\n     * 模块名（逻辑服名）\n     * <pre>\n     *     注意，如果没设置 tag，此名也会是 tag 名\n     *     see: {@link BrokerClientBuilder#tag}\n     * </pre>\n     */\n    String appName;\n    /** 业务框架 */\n    BarSkeleton barSkeleton;\n    /** 逻辑服类型 */\n    BrokerClientType brokerClientType = BrokerClientType.LOGIC;\n    /** 连接 broker （游戏网关） 的地址 */\n    BrokerAddress brokerAddress;\n\n    /** 消息发送超时时间 */\n    int timeoutMillis = IoGameGlobalConfig.timeoutMillis;\n\n    /** bolt 业务处理器的钩子管理器 */\n    ClientProcessorHooks clientProcessorHooks;\n    /** 管理 bolt client */\n    BrokerClientManager brokerClientManager;\n    /** aware 注入扩展 */\n    AwareInject awareInject;\n    /** 逻辑服状态 */\n    int status = BrokerClientStatusConfig.normal;\n    int withNo;\n\n    BrokerClientBuilder() {\n    }\n\n    /**\n     * 添加连接器\n     *\n     * @param type              type\n     * @param processorSupplier 连接器\n     * @return this\n     */\n    public BrokerClientBuilder addConnectionEventProcessor(ConnectionEventType type, Supplier<ConnectionEventProcessor> processorSupplier) {\n        this.connectionEventProcessorMap.put(type, processorSupplier);\n        return this;\n    }\n\n    /**\n     * 注册处理器\n     *\n     * @param processorSupplier 处理器\n     * @return this\n     */\n    public BrokerClientBuilder registerUserProcessor(Supplier<UserProcessor<?>> processorSupplier) {\n        this.processorList.add(processorSupplier);\n        return this;\n    }\n\n    /**\n     * 移除不需要的处理器\n     *\n     * @param processorClass 处理器\n     * @return this\n     */\n    public BrokerClientBuilder removeUserProcessor(Class<? extends UserProcessor<?>> processorClass) {\n        this.removeProcessorList.add(processorClass);\n        return this;\n    }\n\n    /**\n     * 添加监听\n     *\n     * @param listener listener\n     * @return this\n     */\n    public BrokerClientBuilder addListener(BrokerClientListener listener) {\n        this.brokerClientListenerRegion.add(listener);\n        return this;\n    }\n\n    /**\n     * 构建 BrokerClient\n     *\n     * @param brokerAddress 连接 broker （游戏网关） 的地址\n     * @return BrokerClient\n     */\n    public BrokerClient build(BrokerAddress brokerAddress) {\n        this.brokerAddress = brokerAddress;\n        return build();\n    }\n\n    public BrokerClient build() {\n\n        this.settingDefaultValue();\n\n        // 模块信息 （子游戏服的信息、逻辑服）\n        BrokerClientModuleMessage brokerClientModuleMessage = this.createBrokerClientMessage();\n        SimpleServerInfo simpleServerInfo = this.createSimpleServerInfo();\n\n        BrokerClient brokerClient = new BrokerClient()\n                .setId(this.id)\n                .setTag(this.tag)\n                .setAppName(this.appName)\n                .setBarSkeleton(this.barSkeleton)\n                .setBrokerAddress(this.brokerAddress)\n                .setBrokerClientType(this.brokerClientType)\n                .setBrokerClientModuleMessage(brokerClientModuleMessage)\n                .setBrokerClientListenerRegion(this.brokerClientListenerRegion)\n                .setTimeoutMillis(this.timeoutMillis)\n                .setConnectionEventProcessorMap(this.connectionEventProcessorMap)\n                .setProcessorList(this.processorList)\n                .setClientProcessorHooks(this.clientProcessorHooks)\n                .setBrokerClientManager(this.brokerClientManager)\n                .setSimpleServerInfo(simpleServerInfo)\n                .setAwareInject(this.awareInject)\n                .setStatus(this.status);\n\n        brokerClient.option(BrokerClientAttr.onlineListenerRecordSet, new NonBlockingHashSet<>());\n        brokerClient.option(BrokerClientAttr.offlineListenerRecordSet, new NonBlockingHashSet<>());\n        brokerClient.setWithNo(this.withNo);\n\n        // 保存一下 BrokerClient 的引用\n        if (this.brokerClientType == BrokerClientType.LOGIC) {\n            BrokerClientHelper.brokerClient = brokerClient;\n        }\n\n        return brokerClient;\n    }\n\n    private void settingDefaultValue() {\n\n        if (Objects.isNull(this.id)) {\n            this.id = UUID.randomUUID().toString();\n        }\n\n        if (Objects.isNull(this.appName)) {\n            ThrowKit.ofRuntimeException(\"Please set the appName\");\n        }\n\n        if (Objects.isNull(this.tag)) {\n            this.tag = this.appName;\n        }\n\n        if (Objects.isNull(this.clientProcessorHooks)) {\n            // 因为目前 clientProcess 的 hook 只有一个，暂时这样处理着\n            this.clientProcessorHooks = new ClientProcessorHooks();\n        }\n\n        // 处理分布式事件总线的 listener\n        this.addListener(new EventBusBrokerClientListener());\n\n        for (Class<?> removeClass : this.removeProcessorList) {\n            Iterator<Supplier<UserProcessor<?>>> iterator = this.processorList.iterator();\n            while (iterator.hasNext()) {\n                Supplier<UserProcessor<?>> next = iterator.next();\n                Class<?> processorClass = next.get().getClass();\n\n                if (Objects.equals(processorClass, removeClass)) {\n                    iterator.remove();\n                    break;\n                }\n            }\n        }\n    }\n\n    private List<Integer> listCmdMerge() {\n        // 业务框架\n        if (Objects.nonNull(barSkeleton)) {\n            // 设置模块包含的 cmd 列表\n            var actionCommandRegions = barSkeleton.getActionCommandRegions();\n            return actionCommandRegions.listCmdMerge();\n        }\n\n        return Collections.emptyList();\n    }\n\n    /**\n     * 初始化 模块信息\n     *\n     * @return 模块信息\n     */\n    private BrokerClientModuleMessage createBrokerClientMessage() {\n        var cmdMergeList = this.listCmdMerge();\n\n        return new BrokerClientModuleMessage()\n                .setId(this.id)\n                .setName(this.appName)\n                .setAddress(NetworkKit.LOCAL_IP)\n                .setCmdMergeList(cmdMergeList)\n                .setBrokerClientType(this.brokerClientType)\n                .setTag(this.tag)\n                .setStatus(this.status)\n                .setWithNo(this.withNo)\n                .setIoGamePid(ioGamePidRandom)\n                ;\n    }\n\n    private SimpleServerInfo createSimpleServerInfo() {\n        SimpleServerInfo simpleServerInfo = new SimpleServerInfo();\n        simpleServerInfo.setId(this.id);\n        simpleServerInfo.setName(this.appName);\n        simpleServerInfo.setTag(this.tag);\n        simpleServerInfo.setIdHash(HashKit.hash32(this.id));\n        simpleServerInfo.setBrokerClientType(this.brokerClientType.name());\n        simpleServerInfo.setStartTime(System.currentTimeMillis());\n        simpleServerInfo.setStatus(this.status);\n\n        return simpleServerInfo;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClientHelper.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.iohao.game.action.skeleton.core.commumication.*;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport lombok.experimental.UtilityClass;\n\n/**\n * 游戏逻辑服 BrokerClient 的引用持有\n * <pre>\n *     会在 {@link BrokerClientBuilder#build()} 时赋值\n *\n *     对于多个 BrokerClient 的引用管理，可以参考 {@link BrokerClients}\n *\n *     这个类的主要作用是为了更好的区分框架提供的通讯方式\n *     让开发者在使用时更加的清晰\n * </pre>\n * 注意\n * <pre>\n *     如果一个进程中启动了多个逻辑服，会随机选一个作为 brokerClient 的引用。\n *\n *     如果想获取当前 action 所关联的游戏逻辑服，可以通过 FlowContext 获取\n *     参考 {@link FlowContext#getBroadcastContext()}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@UtilityClass\npublic class BrokerClientHelper {\n    BrokerClient brokerClient;\n\n    public BrokerClientContext getBrokerClient() {\n        return brokerClient;\n    }\n\n    public ProcessorContext getProcessorContext() {\n        return brokerClient.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 广播通讯上下文\n     *\n     * @return BroadcastContext\n     */\n    public BroadcastContext getBroadcastContext() {\n        return brokerClient.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 广播通讯上下文 - 严格顺序的\n     *\n     * @return BroadcastOrderContext\n     */\n    public BroadcastOrderContext getBroadcastOrderContext() {\n        return brokerClient.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 游戏逻辑服与游戏逻辑服之间的通讯上下文\n     *\n     * @return InvokeModuleContext\n     */\n    public InvokeModuleContext getInvokeModuleContext() {\n        return brokerClient.getCommunicationAggregationContext();\n    }\n\n    /**\n     * 游戏逻辑服与游戏对外服的通讯上下文\n     *\n     * @return InvokeExternalModuleContext\n     */\n    public InvokeExternalModuleContext getInvokeExternalModuleContext() {\n        return brokerClient.getCommunicationAggregationContext();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClientItem.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.alipay.remoting.Connection;\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.alipay.remoting.config.BoltClientOption;\nimport com.alipay.remoting.exception.RemotingException;\nimport com.alipay.remoting.rpc.RpcClient;\nimport com.alipay.remoting.rpc.protocol.UserProcessor;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.SkeletonAttr;\nimport com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.action.skeleton.protocol.collect.RequestCollectMessage;\nimport com.iohao.game.action.skeleton.protocol.collect.ResponseCollectMessage;\nimport com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;\nimport com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalMessage;\nimport com.iohao.game.action.skeleton.pulse.Pulses;\nimport com.iohao.game.action.skeleton.pulse.core.consumer.PulseConsumers;\nimport com.iohao.game.action.skeleton.pulse.core.producer.PulseProducers;\nimport com.iohao.game.bolt.broker.core.aware.*;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientItemConnectMessage;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.InnerModuleMessage;\nimport com.iohao.game.bolt.broker.core.message.InnerModuleVoidMessage;\nimport com.iohao.game.common.kit.CollKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.Serializable;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 客户连接项\n * <pre>\n *     与游戏网关是 1:1 的关系\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Slf4j\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClientItem implements CommunicationAggregationContext, AwareInject {\n    public enum Status {\n        /** 活跃 */\n        ACTIVE,\n        /** 断开：与网关断开了 */\n        DISCONNECT;\n    }\n\n    final RpcClient rpcClient;\n    /** 广播 */\n    final Broadcast broadcast = new Broadcast(this);\n\n    /** 与 broker 通信的连接 */\n    Connection connection;\n    /** ip:port */\n    String address;\n    /** 消息发送超时时间 */\n    int timeoutMillis = IoGameGlobalConfig.timeoutMillis;\n    /** 业务框架 */\n    BarSkeleton barSkeleton;\n    /** broker 的 client */\n    BrokerClient brokerClient;\n\n    Status status = Status.DISCONNECT;\n    /** aware 注入扩展 */\n    AwareInject awareInject;\n    int brokerServerWithNo;\n\n    public BrokerClientItem(String address) {\n        this.address = address;\n        this.rpcClient = new RpcClient();\n        // 重连选项\n        rpcClient.option(BoltClientOption.CONN_RECONNECT_SWITCH, true);\n        rpcClient.option(BoltClientOption.CONN_MONITOR_SWITCH, true);\n    }\n\n    public Object invokeSync(final Object request, final int timeoutMillis) throws RemotingException, InterruptedException {\n        return rpcClient.invokeSync(connection, request, timeoutMillis);\n    }\n\n    public Object invokeSync(final Object request) throws RemotingException, InterruptedException {\n        return invokeSync(request, timeoutMillis);\n    }\n\n    public void oneway(final Object request) throws RemotingException {\n        this.rpcClient.oneway(connection, request);\n    }\n\n    void invokeWithCallback(Object request) throws RemotingException {\n        this.rpcClient.invokeWithCallback(connection, request, null, timeoutMillis);\n    }\n\n    @Override\n    public void broadcast(ResponseMessage responseMessage, Collection<Long> userIdList) {\n\n        if (CollKit.isEmpty(userIdList)) {\n            log.warn(\"广播无效 userIdList : {}\", userIdList);\n            return;\n        }\n\n        this.broadcast.broadcast(responseMessage, userIdList);\n    }\n\n    @Override\n    public void broadcast(ResponseMessage responseMessage, long userId) {\n        this.broadcast.broadcast(responseMessage, userId);\n    }\n\n    @Override\n    public void broadcast(ResponseMessage responseMessage) {\n        this.broadcast.broadcast(responseMessage);\n    }\n\n    @Override\n    public void broadcastOrder(ResponseMessage responseMessage, Collection<Long> userIdList) {\n        this.broadcast.broadcastOrder(responseMessage, userIdList);\n    }\n\n    @Override\n    public void broadcastOrder(ResponseMessage responseMessage, long userId) {\n        this.broadcast.broadcastOrder(responseMessage, userId);\n    }\n\n    @Override\n    public void broadcastOrder(ResponseMessage responseMessage) {\n        this.broadcast.broadcastOrder(responseMessage);\n    }\n\n    @Override\n    public ResponseMessage invokeModuleMessage(RequestMessage requestMessage) {\n        try {\n            InnerModuleMessage moduleMessage = new InnerModuleMessage();\n            moduleMessage.setRequestMessage(requestMessage);\n            return (ResponseMessage) this.invokeSync(moduleMessage);\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n            var responseMessage = requestMessage.createResponseMessage();\n            responseMessage.setResponseStatus(ActionErrorEnum.systemOtherErrCode.getCode());\n            responseMessage.setValidatorMsg(e.getMessage());\n            return responseMessage;\n        }\n    }\n\n    @Override\n    public void invokeModuleVoidMessage(RequestMessage requestMessage) {\n        try {\n            InnerModuleVoidMessage moduleVoidMessage = new InnerModuleVoidMessage();\n            moduleVoidMessage.setRequestMessage(requestMessage);\n            this.oneway(moduleVoidMessage);\n        } catch (RemotingException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public ResponseCollectMessage invokeModuleCollectMessage(RequestMessage requestMessage) {\n\n        RequestCollectMessage requestCollectMessage = new RequestCollectMessage()\n                .setRequestMessage(requestMessage);\n\n        try {\n            return (ResponseCollectMessage) this.invokeSync(requestCollectMessage);\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n            ResponseCollectMessage responseCollectMessage = new ResponseCollectMessage();\n            responseCollectMessage.setStatusCode(ActionErrorEnum.systemOtherErrCode.getCode());\n            responseCollectMessage.setStatusMes(e.getMessage());\n            responseCollectMessage.setMessageList(Collections.emptyList());\n\n            return responseCollectMessage;\n        }\n    }\n\n    @Override\n    public ResponseCollectExternalMessage invokeExternalModuleCollectMessage(int bizCode, Serializable data) {\n        RequestCollectExternalMessage request = new RequestCollectExternalMessage()\n                .setBizCode(bizCode)\n                .setData(data);\n\n        return this.invokeExternalModuleCollectMessage(request);\n    }\n\n    @Override\n    public ResponseCollectExternalMessage invokeExternalModuleCollectMessage(RequestCollectExternalMessage request) {\n        try {\n            return (ResponseCollectExternalMessage) this.invokeSync(request);\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n\n        /*\n         * 给一个空对象，这样调用端可以减少一些 null 判断。\n         * 而且正常情况下，也走不到这里。\n         */\n        return new ResponseCollectExternalMessage();\n    }\n\n    @Override\n    public void invokeOneway(Object message) {\n        this.internalOneway(message);\n    }\n\n    void addConnectionEventProcessor(ConnectionEventType type, ConnectionEventProcessor processor) {\n\n        aware(processor);\n\n        this.rpcClient.addConnectionEventProcessor(type, processor);\n    }\n\n    void registerUserProcessor(UserProcessor<?> processor) {\n\n        aware(processor);\n\n        this.rpcClient.registerUserProcessor(processor);\n    }\n\n    @Override\n    public void aware(Object obj) {\n        /*\n         * 目前 aware 系列由框架提供，\n         * 虽然这里可以开放给开发者来控制，但目前暂时不考虑开放\n         */\n        if (Objects.nonNull(this.awareInject)) {\n            this.awareInject.aware(obj);\n        }\n\n        AwareKit.aware(obj);\n\n        if (obj instanceof BrokerClientItemAware aware) {\n            aware.setBrokerClientItem(this);\n        }\n\n        if (obj instanceof BrokerClientAware aware) {\n            aware.setBrokerClient(this.brokerClient);\n        }\n\n        if (obj instanceof PulseConsumerAware aware) {\n            Pulses pulses = this.barSkeleton.option(SkeletonAttr.pulses);\n            PulseConsumers pulseConsumers = pulses.getPulseConsumers();\n            aware.setPulseConsumers(pulseConsumers);\n        }\n\n        if (obj instanceof PulseProducerAware aware) {\n            Pulses pulses = this.barSkeleton.option(SkeletonAttr.pulses);\n            PulseProducers pulseProducers = pulses.getPulseProducers();\n            aware.setPulseProducers(pulseProducers);\n        }\n    }\n\n    private void internalOneway(Object responseObject) {\n        try {\n            rpcClient.oneway(connection, responseObject);\n        } catch (RemotingException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 注册到网关 broker\n     * 客户端服务器注册到网关服\n     */\n    public void registerToBroker() {\n        try {\n            // 客户端服务器注册到游戏网关服\n            BrokerClientModuleMessage brokerClientModuleMessage = this.brokerClient.getBrokerClientModuleMessage();\n            this.rpcClient.oneway(address, brokerClientModuleMessage);\n\n            TimeUnit.MILLISECONDS.sleep(100);\n            this.status = Status.ACTIVE;\n            this.brokerClient.getBrokerClientManager().resetSelector();\n\n            this.barSkeleton.getRunners().onStartAfter();\n\n            this.with();\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    public void startup() {\n        this.rpcClient.startup();\n        this.send();\n    }\n\n    private void send() {\n        try {\n            var message = new BrokerClientItemConnectMessage();\n            this.rpcClient.oneway(address, message);\n        } catch (RemotingException | InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    private void with() {\n        int withNo = this.brokerClient.getWithNo();\n\n        if (withNo == 0 || withNo != this.brokerServerWithNo) {\n            this.brokerServerWithNo = 0;\n            return;\n        }\n\n        // 连接与当前 brokerClientItem 是同一个进程的。\n        BrokerClientManager manager = brokerClient.getBrokerClientManager();\n        manager.setBrokerClientItemWith(this);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClientManager.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.alipay.remoting.ConnectionEventProcessor;\nimport com.alipay.remoting.ConnectionEventType;\nimport com.alipay.remoting.rpc.protocol.UserProcessor;\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.core.loadbalance.ElementSelector;\nimport com.iohao.game.bolt.broker.core.loadbalance.ElementSelectorFactory;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * 管理 bolt client ， 接收新的集群信息，并增减相关 bolt client\n *\n * <pre>\n *     BrokerClientItem 与游戏网关是 1:1 的关系，\n *     如果启动了 N 个网关，那么 BrokerClientManager 下的 BrokerClientItem 就会有 N 个。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Slf4j\n@Getter\n@Setter\n@Accessors(chain = true)\npublic final class BrokerClientManager {\n    /**\n     * <pre>\n     *     key : ip:port，broker 的地址。\n     *     value : 与 broker 建立连接的 bolt client\n     * </pre>\n     */\n    final Map<String, BrokerClientItem> brokerClientItemMap = new NonBlockingHashMap<>();\n    /** 连接 broker （游戏网关） 的地址 */\n    BrokerAddress brokerAddress;\n    /** 连接事件 */\n    Map<ConnectionEventType, Supplier<ConnectionEventProcessor>> connectionEventProcessorMap;\n    /** 用户处理器 */\n    List<Supplier<UserProcessor<?>>> processorList;\n    /** 业务框架 */\n    BarSkeleton barSkeleton;\n    /** 元素选择器生产工厂 */\n    ElementSelectorFactory<BrokerClientItem> elementSelectorFactory = ElementSelector::of;\n    /** BrokerClientItem 元素选择器 */\n    ElementSelector<BrokerClientItem> elementSelector;\n    /** 消息发送超时时间 */\n    int timeoutMillis;\n    BrokerClient brokerClient;\n\n    BrokerClientItem brokerClientItemWith;\n\n    public void init() {\n        this.elementSelector = elementSelectorFactory.createElementSelector(Collections.emptyList());\n\n        this.register(this.brokerAddress.getAddress());\n    }\n\n    public void register(String address) {\n        BrokerClientItem brokerClientItem = new BrokerClientItem(address)\n                .setTimeoutMillis(this.timeoutMillis)\n                .setBarSkeleton(this.barSkeleton)\n                .setBrokerClient(this.brokerClient)\n                .setAwareInject(this.brokerClient.getAwareInject());\n\n        // 添加连接处理器\n        connectionEventProcessorMap.forEach((type, valueSupplier) -> {\n            var processor = valueSupplier.get();\n            brokerClientItem.addConnectionEventProcessor(type, processor);\n        });\n\n        // 注册用户处理器\n        processorList.stream()\n                .map(Supplier::get)\n                .forEach(brokerClientItem::registerUserProcessor);\n\n        // 初始化\n        brokerClientItem.startup();\n\n        register(brokerClientItem);\n    }\n\n    public void register(BrokerClientItem brokerClientItem) {\n        String address = brokerClientItem.getAddress();\n        // 添加映射关系\n        this.brokerClientItemMap.put(address, brokerClientItem);\n        // 生成负载对象\n        this.resetSelector();\n    }\n\n    public void remove(String address) {\n        if (IoGameGlobalConfig.openLog) {\n            log.info(\"broker （游戏网关）的机器减少了 address : {}\", address);\n        }\n\n        // 移除\n        this.brokerClientItemMap.remove(address);\n\n        // 生成负载对象\n        this.resetSelector();\n\n        if (IoGameGlobalConfig.openLog) {\n            Set<String> keySet = brokerClientItemMap.keySet();\n            String message = \"当前网关数量 : %s {}\".formatted(this.brokerClientItemMap.size());\n            log.info(message, keySet);\n        }\n    }\n\n    public void remove(BrokerClientItem brokerClientItem) {\n        this.remove(brokerClientItem.getAddress());\n        brokerClientItem.setStatus(BrokerClientItem.Status.DISCONNECT);\n        this.resetSelector();\n    }\n\n    void resetSelector() {\n        // 生成负载对象；注意，这个 List 是不支持序列化的\n        List<BrokerClientItem> brokerClientItems = brokerClientItemMap.values()\n                .stream()\n                .filter(brokerClientItem -> brokerClientItem.getStatus() == BrokerClientItem.Status.ACTIVE)\n                .toList();\n\n        // 重置负载对象\n        this.elementSelector = this.elementSelectorFactory.createElementSelector(brokerClientItems);\n    }\n\n    public int countActiveItem() {\n        return (int) brokerClientItemMap.values()\n                .stream()\n                .filter(brokerClientItem -> brokerClientItem.getStatus() == BrokerClientItem.Status.ACTIVE)\n                .count();\n    }\n\n    public int countItem() {\n        return brokerClientItemMap.size();\n    }\n\n    public BrokerClientItem next() {\n        if (Objects.nonNull(this.brokerClientItemWith)) {\n            return this.brokerClientItemWith;\n        }\n\n        return elementSelector.next();\n    }\n\n    public List<BrokerClientItem> listBrokerClientItem() {\n        return new ArrayList<>(brokerClientItemMap.values());\n    }\n\n    public boolean contains(String address) {\n        return this.brokerClientItemMap.containsKey(address);\n    }\n\n    public Set<String> keySet() {\n        return new HashSet<>(this.brokerClientItemMap.keySet());\n    }\n\n    public void forEach(Consumer<BrokerClientItem> consumer) {\n        this.brokerClientItemMap.values()\n                .stream()\n                .filter(brokerClientItem -> brokerClientItem.getStatus() == BrokerClientItem.Status.ACTIVE)\n                .forEach(consumer);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClientType.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport java.io.Serializable;\n\n/**\n * 逻辑服类型\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\npublic enum BrokerClientType implements Serializable {\n    /** 游戏逻辑服 */\n    LOGIC,\n    /** 游戏对外服 (真实用户连接的服务器) */\n    EXTERNAL\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/BrokerClients.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;\nimport lombok.experimental.UtilityClass;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 管理 BrokerClient\n * <pre>\n *     管理逻辑服，默认情况使用 BrokerClient.id 与 BrokerClient 关联\n *\n *     具体阅读 BrokerClientStartup#startupSuccess 源码\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@UtilityClass\npublic class BrokerClients {\n    final Map<String, BrokerClient> brokerClientMap = new NonBlockingHashMap<>();\n\n    public void put(String id, BrokerClient brokerClient) {\n        Objects.requireNonNull(id);\n        Objects.requireNonNull(brokerClient);\n\n        brokerClientMap.put(id, brokerClient);\n    }\n\n    public BrokerClientContext getBrokerClient(String id) {\n        return brokerClientMap.get(id);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/DefaultProcessorContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\n/**\n * @author 渔民小镇\n * @date 2022-06-04\n */\npublic class DefaultProcessorContext {\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/EventBusBrokerClientListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.SkeletonAttr;\nimport com.iohao.game.action.skeleton.eventbus.EventBrokerClientMessage;\nimport com.iohao.game.action.skeleton.eventbus.EventBus;\nimport com.iohao.game.action.skeleton.eventbus.EventBusRegion;\nimport com.iohao.game.action.skeleton.eventbus.EventTopicMessage;\nimport com.iohao.game.bolt.broker.core.common.processor.listener.BrokerClientListener;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.common.kit.CollKit;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * 分布式事件总线的 listener\n *\n * @author 渔民小镇\n * @date 2023-12-24\n */\n@Slf4j\nfinal class EventBusBrokerClientListener implements BrokerClientListener {\n    String eventBusTopicName = \"eventBusTopicSet\";\n    boolean theLog;\n\n    @Override\n    public void registerBefore(BrokerClientModuleMessage moduleMessage, BrokerClient client) {\n        BarSkeleton barSkeleton = client.getBarSkeleton();\n        EventBus eventBus = barSkeleton.option(SkeletonAttr.eventBus);\n\n        if (eventBus == null) {\n            return;\n        }\n\n        // 记录当前 EventBus 的订阅者信息到 moduleMessage 中\n        Set<String> topicSet = eventBus.listTopic();\n\n        if (CollKit.isEmpty(topicSet)) {\n            return;\n        }\n\n        moduleMessage.addHeader(this.eventBusTopicName, topicSet);\n    }\n\n    @Override\n    public void offlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        eventBusProcess(otherModuleMessage, client, result -> {\n            if (theLog) {\n                log.info(\"##卸载## {} 卸载 远程订阅者 {}\", client.getAppName(), otherModuleMessage.getName());\n            }\n\n            EventBrokerClientMessage eventBrokerClientMessage = result.eventBrokerClientMessage();\n            EventBusRegion.unloadRemoteTopic(eventBrokerClientMessage);\n        });\n    }\n\n    @Override\n    public void onlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        eventBusProcess(otherModuleMessage, client, result -> {\n            if (theLog) {\n                log.info(\"##加载## {} 加载 远程订阅者 {}\", client.getAppName(), otherModuleMessage.getName());\n            }\n\n            EventBrokerClientMessage eventBrokerClientMessage = result.eventBrokerClientMessage();\n            EventBusRegion.loadRemoteEventTopic(eventBrokerClientMessage);\n        });\n    }\n\n    private void eventBusProcess(BrokerClientModuleMessage otherModuleMessage, BrokerClient client, Consumer<Result> consumer) {\n        Result result = getResult(otherModuleMessage, client);\n        if (result.process) {\n            consumer.accept(result);\n        }\n    }\n\n    private Result getResult(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        Set<String> topicSet = otherModuleMessage.getHeader(this.eventBusTopicName);\n        if (CollKit.isEmpty(topicSet)) {\n            // 说明该逻辑服没有订阅者，不做任何处理\n            return new Result(false, null);\n        }\n\n        // 检查该逻辑服是否在同一个进程中\n        BrokerClientModuleMessage moduleMessage = client.getBrokerClientModuleMessage();\n        if (Objects.equals(otherModuleMessage.getIoGamePid(), moduleMessage.getIoGamePid())) {\n            // 在同一个进程中，不做处理\n            return new Result(false, null);\n        }\n\n        // 将其他进程的订阅者主题添加到管理域中\n        EventBrokerClientMessage eventBrokerClientMessage = createEventBrokerClientMessage(otherModuleMessage, topicSet);\n\n        return new Result(true, eventBrokerClientMessage);\n    }\n\n    private EventBrokerClientMessage createEventBrokerClientMessage(BrokerClientModuleMessage otherModuleMessage, Set<String> topicSet) {\n        String id = otherModuleMessage.getId();\n        String appName = otherModuleMessage.getName();\n        String tag = otherModuleMessage.getTag();\n        String typeName = otherModuleMessage.getBrokerClientType().name();\n\n        EventBrokerClientMessage eventBrokerClientMessage = new EventBrokerClientMessage(appName, tag, typeName, id);\n        eventBrokerClientMessage.setRemote(true);\n        eventBrokerClientMessage.setEventTopicMessage(new EventTopicMessage(topicSet));\n\n        return eventBrokerClientMessage;\n    }\n\n    private record Result(boolean process, EventBrokerClientMessage eventBrokerClientMessage) {\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/client/config/BrokerClientStatusConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.client.config;\n\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 逻辑服状态配置\n * <pre>\n *     注意，对于状态的处理，目前还没有提供逻辑实现。\n *\n *     状态不使用枚举是因为这样更具备灵活性。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-05-23\n */\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class BrokerClientStatusConfig {\n    /**\n     * 逻辑服状态：正常\n     * <pre>\n     *     当状态是【正常】时，正常状态的游戏逻辑服可以被任何玩家访问！\n     * </pre>\n     */\n    public static int normal = 1;\n\n    /**\n     * 逻辑服状态：维护中\n     * <pre>\n     *     当状态是【维护中】时，不对正常玩家开放的游戏逻辑服，但绑定中的玩家还可以正常访问。\n     *     如果你打算停服维护，可以将状态设置为此状态，直到没有请求可以消费后就可以去停服了！\n     *\n     *     使用场景：\n     *     服务器维护前，将状态设置为【维护】后，正常玩家将不能访问对应的游戏逻辑服，只有绑定了游戏逻辑服的玩家可以访问。\n     *     当服务器将剩余的请求都消费完成后，开发者就可以 kill 进程了。\n     *\n     *     白话一点就是，玩家A 绑定了游戏逻辑服B，现在我们将游戏逻辑服B 状态改成【维护】，\n     *     其他玩家访问不了了，但是玩家A 可以继续访问，直到玩家A 不需要使用游戏逻辑服B了，开发者就可以把机器给停了。\n     * </pre>\n     */\n    public static int maintenance = 2;\n\n    /**\n     * 逻辑服状态：灰度\n     * <pre>\n     *     当状态是【灰度】时，只有灰度玩家，或连接到灰度游戏对外服的玩家可以访问。\n     *\n     *     使用场景：\n     *     线上测试一些新功能、调试一些在线上才会出现的特定 bug 。\n     *     如果逻辑服是灰度状态的，正常玩家将不能访问灰度服务器，只有灰度玩家才能访问。\n     *     我们还提供了将整个游戏对外服设置为灰度的，这样只要是连接到这个游戏对外服的玩家都可以访问灰度服务器了，是不是很方便！\n     * </pre>\n     */\n    public static int gray = 4;\n\n    public static int all = normal | maintenance | gray;\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/AbstractAsyncUserProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.alipay.remoting.rpc.protocol.AsyncUserProcessor;\nimport com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorAware;\n\nimport java.util.concurrent.Executor;\n\n/**\n * AsyncUserProcessor 父类\n *\n * @author 渔民小镇\n * @date 2022-11-10\n */\npublic abstract class AbstractAsyncUserProcessor<T> extends AsyncUserProcessor<T>\n        implements UserProcessorExecutorAware {\n\n    Executor userProcessorExecutor;\n    boolean inNettyThread;\n\n    @Override\n    public Executor getExecutor() {\n        return this.userProcessorExecutor;\n    }\n\n    @Override\n    public Executor getUserProcessorExecutor() {\n        return this.userProcessorExecutor;\n    }\n\n    @Override\n    public void setUserProcessorExecutor(Executor executor) {\n        this.userProcessorExecutor = executor;\n    }\n\n    @Override\n    public void setInNettyThread(boolean inNettyThread) {\n        this.inNettyThread = inNettyThread;\n    }\n\n    @Override\n    public boolean inNettyThreadExecute() {\n        return this.inNettyThread;\n    }\n\n    @Override\n    public boolean processInIOThread() {\n        return inNettyThread;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/DefaultUserProcessorExecutorSelectorStrategy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.alipay.remoting.CustomSerializerManager;\nimport com.alipay.remoting.DefaultCustomSerializer;\nimport com.alipay.remoting.InvokeContext;\nimport com.alipay.remoting.rpc.RequestCommand;\nimport com.alipay.remoting.rpc.protocol.RpcRequestCommand;\nimport com.iohao.game.action.skeleton.kit.ExecutorSelectKit;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.common.kit.ByteKit;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\nimport java.util.concurrent.Executor;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n * @since 21.15\n */\n@Slf4j\nfinal class DefaultUserProcessorExecutorSelectorStrategy extends DefaultCustomSerializer\n        implements UserProcessorExecutorSelectorStrategy {\n\n    final ProcessorSelectorThreadExecutorRegion threadExecutorRegion = new ProcessorSelectorThreadExecutorRegion();\n\n    @Override\n    public <T extends RequestCommand> boolean serializeHeader(T request, InvokeContext invokeContext) {\n        if (request instanceof RpcRequestCommand command) {\n            RequestMessage message = (RequestMessage) command.getRequestObject();\n            HeadMetadata headMetadata = message.getHeadMetadata();\n\n            if (Objects.isNull(headMetadata.getUserProcessorExecutorSelectorBytes())) {\n                // 做一个简单的优化，避免多次序列化\n                long executorIndex = ExecutorSelectKit.getExecutorIndex(headMetadata);\n                headMetadata.setUserProcessorExecutorSelectorBytes(ByteKit.toBytes(executorIndex));\n            }\n\n            command.setHeader(headMetadata.getUserProcessorExecutorSelectorBytes());\n\n            return true;\n        }\n\n        return false;\n    }\n\n    @Override\n    public <T extends RequestCommand> boolean deserializeHeader(T request) {\n        if (request instanceof RpcRequestCommand command) {\n            byte[] header = command.getHeader();\n            long executorIndex = ByteKit.getLong(header);\n            command.setRequestHeader(executorIndex);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    @Override\n    public Executor select(String requestClass, Object requestHeader) {\n        if (Objects.isNull(requestHeader)) {\n            return null;\n        }\n\n        // see RpcRequestProcessor.java:105\n        long executorIndex = (long) requestHeader;\n        return threadExecutorRegion.getThreadExecutor(executorIndex).executor();\n    }\n\n    private DefaultUserProcessorExecutorSelectorStrategy() {\n        // 自定义序列化解析\n        CustomSerializerManager.registerCustomSerializer(RequestMessage.class.getName(), this);\n    }\n\n    public static DefaultUserProcessorExecutorSelectorStrategy me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DefaultUserProcessorExecutorSelectorStrategy ME = new DefaultUserProcessorExecutorSelectorStrategy();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/DefaultUserProcessorExecutorStrategy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorAware;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.RuntimeKit;\nimport com.iohao.game.common.kit.concurrent.DaemonThreadFactory;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 默认策略\n *\n * @author 渔民小镇\n * @date 2022-11-11\n */\n@Slf4j\nfinal class DefaultUserProcessorExecutorStrategy implements UserProcessorExecutorStrategy {\n    final Map<String, Executor> executorMap = new NonBlockingHashMap<>();\n\n    @Override\n    public Executor getExecutor(UserProcessorExecutorAware userProcessorExecutorAware) {\n\n        if (userProcessorExecutorAware.inNettyThreadExecute()) {\n            /*\n             * 当 inNettyThreadExecute 为 true 时，即使设置了执行器也是无效的。\n             * 如果开发者使用自定义的线程执行器，需要将 inNettyThreadExecute 设置为 false。\n             *\n             * 如果将 inNettyThreadExecute 设置为 false，会有两种情况\n             *     1. 如果配置了自定义的线程执行器，则会优先使用自定义的线程执行器来执行业务；\n             *     2. 如果没有配置，也就是返回 null，将会使用 bolt 默认的 ioThreadExecutor ；具体阅读 ProcessorManager.defaultExecutor 相关源码\n             */\n            return null;\n        }\n\n        String userProcessorName = userProcessorExecutorAware.getClass().getSimpleName();\n\n        return switch (userProcessorName) {\n            // RequestMessage 相关的单独一个池，使用单线程传递请求消息。\n            case \"RequestMessageClientProcessor\" -> ofExecutorRequestMessage();\n            case \"RequestMessageBrokerProcessor\" -> ofExecutorRequestMessage();\n            // 其他 UserProcessor 使用 common 多线程消费任务\n            default -> ofExecutorCommon();\n        };\n    }\n\n    private Executor ofExecutorRequestMessage() {\n        // 使用单线程传递请求消息\n        return ofExecutorCommon(\"RequestMessage\", 1);\n    }\n\n    private Executor ofExecutorCommon() {\n        int corePoolSize = RuntimeKit.availableProcessors;\n        return ofExecutorCommon(\"common\", corePoolSize);\n    }\n\n    private Executor ofExecutorCommon(String name, int corePoolSize) {\n        Executor executor = this.executorMap.get(name);\n\n        if (Objects.isNull(executor)) {\n            var tempExecutor = createExecutor(name, corePoolSize, corePoolSize);\n            executor = MoreKit.firstNonNull(this.executorMap.putIfAbsent(name, tempExecutor), tempExecutor);\n\n            if (executor != tempExecutor) {\n                // 引用不相等就 shutdown\n                ((ThreadPoolExecutor) tempExecutor).shutdown();\n            }\n        }\n\n        return executor;\n    }\n\n    private Executor createExecutor(String userProcessorName, int corePoolSize, int maximumPoolSize) {\n\n        /*\n         * 下面对于 UserProcessor 提供了一些默认的 Executor 配置，\n         * 开发者可以根据自身业务需要来定制 UserProcessorExecutorStrategy。\n         */\n\n        String namePrefix = String.format(\"Processor-Executor-%s-%d\"\n                , userProcessorName\n                , maximumPoolSize);\n\n        DaemonThreadFactory threadFactory = new DaemonThreadFactory(namePrefix);\n\n        var executor = new ThreadPoolExecutor(\n                corePoolSize, maximumPoolSize,\n                60L, TimeUnit.SECONDS,\n                new LinkedBlockingQueue<>(),\n                threadFactory);\n\n        // Processor-Executor\n        log.debug(\"{} 【corePoolSize:{}】【maximumPoolSize:{}】 \",\n                namePrefix,\n                corePoolSize,\n                maximumPoolSize\n        );\n\n        // 小预热\n        for (int i = 0; i < corePoolSize; i++) {\n            executor.execute(() -> {\n            });\n        }\n\n        return executor;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/IoGameGlobalConfig.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.iohao.game.action.skeleton.core.IoGameCommonCoreConfig;\nimport com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorAware;\nimport lombok.Getter;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\nimport java.util.concurrent.Executor;\n\n/**\n * ioGame 全局默认配置\n *\n * @author 渔民小镇\n * @date 2022-11-11\n */\n@UtilityClass\npublic class IoGameGlobalConfig {\n    /** broker （游戏网关）默认端口 */\n    public int brokerPort = 10200;\n    /** bolt 消息发送超时时间 */\n    public int timeoutMillis = 3000;\n    /** 集群默认监听端口 Gossip listen port */\n    public int gossipListenPort = 30056;\n    /** true 开启日志 */\n    public boolean openLog = true;\n    /** true 开启请求响应相关日志，默认为 false */\n    public boolean requestResponseLog;\n    /** true 开启对外服相关日志，默认为 false */\n    public boolean externalLog;\n    /** true 开启广播相关日志，默认为 false */\n    public boolean broadcastLog;\n    /**\n     * Broker（游戏网关）转发消息容错配置\n     * <pre>\n     *     游戏逻辑服与游戏对外服通信时，如果没有明确指定要通信游戏对外服，游戏网关则会将消息转发到所有的游戏对外服上。\n     *     如果指定了游戏对外服的，游戏网关则会将消息转发到该游戏对外服上，而不会将消息转发到所有的对外服上。\n     *\n     *     当为 true 时，开启容错机制\n     *         表示开发者在发送消息时，如果指定了游戏对外服的，\n     *         但【游戏网关】中没有找到所指定的【游戏对外服】，则会将消息转发到所有的游戏对外服上，\n     *         这么做的目的是，即使开发者填错了指定的游戏对外服，也能保证消息可以送达到游戏对外服。\n     *\n     *     当为 false 时，关闭容错机制\n     *         表示在【游戏网关】中找不到指定的【游戏对外服】时，则不管了。\n     *\n     *     支持的通讯方式场景\n     *         广播\n     *         游戏对外服的数据与扩展\n     * </pre>\n     * 另一种叙述版本\n     * <pre>\n     *     作用：\n     *         在游戏逻辑服发送广播时，支持指定游戏对外服来广播；\n     *         如果你能事先知道所要广播的游戏对外服，那么在广播时通过指定游戏对外服，可以避免一些无效的转发。\n     *\n     *         为了更好的理解的这个配置的作用，这里将作一些比喻：\n     *         1. 将广播时指定的游戏对外服，看作是目标\n     *         2. 将发送广播的游戏逻辑服，看作是命令\n     *         3. 而 Broker（游戏网关）职责是对消息做转发，可看成是一名射击员；射击员手上有两把枪，分别是狙击枪和 AK47。\n     *\n     *         狙击枪的作用是单点目标，而 AK47 的作用则是扫射多个目标（就是所有的游戏对外服）。\n     *\n     *     场景一：\n     *         当设置为 true 时，表示射击员可以将手中的狙击切换为 AK47，什么意思呢？\n     *         意思就是如果在【游戏网关】中没有找到所指定的【游戏对外服】，则将广播数据发送给【所有的游戏对外服】。（换 AK 来扫射）\n     *         这么做的目的是，即使开发者填错了指定的游戏对外服，也能保证消息可以送达到游戏对外服。\n     *\n     *     场景二：\n     *         当设置为 false 时，表示找不到指定的【游戏对外服】时，则不管了。\n     * </pre>\n     */\n    public boolean brokerSniperToggleAK47 = true;\n\n    /** true 开启集群相关日志 */\n    public boolean brokerClusterLog;\n    /** true 使用调度器打印集群信息，默认 30 秒打印一次（目前不提供打印频率设置） */\n    public boolean brokerClusterFixedRateLog;\n    /** true 表示开启 traceId 特性 */\n    public boolean openTraceId;\n\n    @Getter\n    boolean eventBusLog;\n\n    /**\n     * UserProcessor 构建 Executor 的策略\n     * <pre>\n     *     默认使用 DefaultUserProcessorExecutorStrategy 实现类，\n     *     内容使用 Executors.newVirtualThreadPerTaskExecutor()\n     * </pre>\n     *\n     * @see DefaultUserProcessorExecutorStrategy\n     * @see VirtualThreadUserProcessorExecutorStrategy\n     */\n    public UserProcessorExecutorStrategy userProcessorExecutorStrategy = new DefaultUserProcessorExecutorStrategy();\n\n    public Executor getExecutor(UserProcessorExecutorAware userProcessorExecutorAware) {\n\n        if (Objects.isNull(userProcessorExecutorStrategy)) {\n            // 不使用任何策略\n            return null;\n        }\n\n        return userProcessorExecutorStrategy.getExecutor(userProcessorExecutorAware);\n    }\n\n    boolean userProcessorExecutorSelectorEnable;\n\n    public void enableUserProcessorExecutorSelector() {\n        userProcessorExecutorSelectorEnable = true;\n    }\n\n    public UserProcessorExecutorSelectorStrategy userProcessorExecutorSelectorStrategy;\n\n    public UserProcessorExecutorSelectorStrategy getExecutorSelector() {\n\n        if (!userProcessorExecutorSelectorEnable) {\n            return null;\n        }\n\n        if (Objects.isNull(userProcessorExecutorSelectorStrategy)) {\n            userProcessorExecutorSelectorStrategy = DefaultUserProcessorExecutorSelectorStrategy.me();\n        }\n\n        return userProcessorExecutorSelectorStrategy;\n    }\n\n    public boolean isExternalLog() {\n        return openLog && externalLog;\n    }\n\n    public boolean isBrokerClusterLog() {\n        return openLog && brokerClusterLog;\n    }\n\n    public boolean isBrokerClusterFixedRateLog() {\n        return openLog && brokerClusterFixedRateLog;\n    }\n\n    public void setEventBusLog(boolean eventBusLog) {\n        IoGameGlobalConfig.eventBusLog = eventBusLog;\n        IoGameCommonCoreConfig.eventBusLog = eventBusLog;\n    }\n\n    /** 框架内部使用，开发者不要使用 */\n    public interface InternalConfig {\n        long executorIndex = 0;\n        long clusterExecutorIndex = 2;\n        long connectIndex = clusterExecutorIndex;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/ProcessorSelectorThreadExecutorRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.iohao.game.common.kit.RuntimeKit;\nimport com.iohao.game.common.kit.concurrent.FixedNameThreadFactory;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutor;\nimport com.iohao.game.common.kit.concurrent.executor.ThreadExecutorRegion;\n\nimport java.util.concurrent.*;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n * @since 21.15\n */\nfinal class ProcessorSelectorThreadExecutorRegion implements ThreadExecutorRegion {\n    final ThreadExecutor[] threadExecutors;\n    final int executorLength;\n\n    ProcessorSelectorThreadExecutorRegion() {\n        threadExecutors = new ThreadExecutor[RuntimeKit.availableProcessors2n];\n        executorLength = threadExecutors.length - 1;\n\n        String threadName = \"ProcessorSelector\";\n        for (int i = 0; i < threadExecutors.length; i++) {\n            // 线程名：name-线程总数-当前线程编号\n            int threadNo = i + 1;\n            String threadNamePrefix = String.format(\"%s-%s-%s\", threadName, threadExecutors.length, threadNo);\n            var executor = this.createExecutorService(threadNamePrefix);\n            this.threadExecutors[i] = new ThreadExecutor(threadNamePrefix, executor, threadNo);\n        }\n    }\n\n    @Override\n    public ThreadExecutor getThreadExecutor(long executorIndex) {\n        int index = (int) (executorIndex & (this.executorLength));\n        return this.threadExecutors[index];\n    }\n\n    private ExecutorService createExecutorService(String threadNamePrefix) {\n        return new ThreadPoolExecutor(1, 1,\n                0L, TimeUnit.MILLISECONDS,\n                new LinkedBlockingQueue<>(),\n                new FixedNameThreadFactory(threadNamePrefix));\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/UserProcessorExecutorSelectorStrategy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.alipay.remoting.rpc.protocol.UserProcessor;\n\n/**\n * @author 渔民小镇\n * @date 2024-08-10\n * @since 21.15\n */\npublic interface UserProcessorExecutorSelectorStrategy\n        extends UserProcessor.ExecutorSelector {\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/UserProcessorExecutorStrategy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorAware;\n\nimport java.util.concurrent.Executor;\n\n/**\n * 主要用于给 UserProcessor 构建 Executor 的策略\n * <pre>\n *     框架会在启动时，如果检测到 UserProcessor 实现了 {@link UserProcessorExecutorAware} 接口，就会触发一次\n *\n *     通过该接口，开发者可以给 UserProcessor 配置 Executor；\n *\n *     开发者可以根据自身业务来做定制\n *     see {@link IoGameGlobalConfig#userProcessorExecutorStrategy}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-11-11\n * @see DefaultUserProcessorExecutorStrategy\n * @see VirtualThreadUserProcessorExecutorStrategy\n */\npublic interface UserProcessorExecutorStrategy {\n    /**\n     * 通过 userProcessorExecutorAware 来得到 Executor\n     * <pre>\n     *     通常用于给 UserProcessor 配置线程池，\n     *     userProcessorExecutorAware 通常是当前的 UserProcessor 实现类\n     * </pre>\n     *\n     * @param userProcessorExecutorAware 通常是 UserProcessor 实现了 UserProcessorExecutorAware 接口\n     * @return Executor\n     */\n    Executor getExecutor(UserProcessorExecutorAware userProcessorExecutorAware);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/VirtualThreadUserProcessorExecutorStrategy.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common;\n\nimport com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorAware;\nimport com.iohao.game.core.common.NetCommonKit;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\n\n/**\n * VirtualThread impl\n *\n * @author 渔民小镇\n * @date 2023-12-16\n * @see Executors#newVirtualThreadPerTaskExecutor()\n */\n@Slf4j\nfinal class VirtualThreadUserProcessorExecutorStrategy implements UserProcessorExecutorStrategy {\n    final Executor virtualExecutor = NetCommonKit.getVirtualExecutor();\n\n    @Override\n    public Executor getExecutor(UserProcessorExecutorAware userProcessorExecutorAware) {\n        return virtualExecutor;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/hook/ClientProcessorHooks.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.hook;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClientBuilder;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * bolt 业务处理器的钩子管理器\n * <pre>\n *     在构建游戏逻辑服赋值\n *\n *     see {@link BrokerClientBuilder#clientProcessorHooks(ClientProcessorHooks)}\n *\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-06-26\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ClientProcessorHooks {\n    /**\n     * 逻辑服业务处理钩子接口\n     * <pre>\n     *     通过业务框架把请求派发给指定的业务类（action）来处理\n     * </pre>\n     */\n    RequestMessageClientProcessorHook requestMessageClientProcessorHook = new DefaultRequestMessageClientProcessorHook();\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/hook/DefaultRequestMessageClientProcessorHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.hook;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.kit.ExecutorSelectKit;\n\n/**\n * 框架提供的 RequestMessageClientProcessorHook 默认实现\n * <pre>\n *     userId 与 Executor 关联，确保同一玩家使用的业务线程是相同的。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-03-31\n */\nfinal class DefaultRequestMessageClientProcessorHook implements RequestMessageClientProcessorHook {\n\n    @Override\n    public void processLogic(BarSkeleton barSkeleton, FlowContext flowContext) {\n        boolean execute = ExecutorSelectKit.processLogic(barSkeleton, flowContext);\n\n        if (!execute) {\n            // 在当前线程中执行业务框架\n            barSkeleton.handle(flowContext);\n        }\n    }\n}"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/hook/RequestMessageClientProcessorHook.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.hook;\n\nimport com.iohao.game.action.skeleton.core.BarSkeleton;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\n\n/**\n * 逻辑服业务处理钩子接口\n *\n * @author 渔民小镇\n * @date 2022-06-26\n */\npublic interface RequestMessageClientProcessorHook {\n\n    /**\n     * 钩子流程逻辑\n     * <pre>\n     *     通过业务框架把请求派发给指定的业务类（action）来处理\n     *\n     *     用于在 bolt 接收请求时，对该请求做一些类似线程编排的事\n     *     当然，这个编排是由开发者自定义的\n     * </pre>\n     *\n     * @param barSkeleton 业务框架\n     * @param flowContext 业务框架 flow 上下文\n     */\n    void processLogic(BarSkeleton barSkeleton, FlowContext flowContext);\n\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/listener/BrokerClientListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.listener;\n\n/**\n * BrokerClient 监听\n *\n * @author 渔民小镇\n * @date 2023-12-14\n * @see LineListener\n * @see ConnectionBeforeListener\n * @see BrokerClientListenerRegion\n */\npublic interface BrokerClientListener extends\n        LineListener,\n        ConnectionBeforeListener {\n}"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/listener/BrokerClientListenerRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.listener;\n\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * BrokerClient 监听管理域\n *\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClientListenerRegion {\n    final Set<BrokerClientListener> listeners = new NonBlockingHashSet<>();\n\n    public boolean isEmpty() {\n        return listeners.isEmpty();\n    }\n\n    public void add(BrokerClientListener listener) {\n        Objects.requireNonNull(listener);\n        this.listeners.add(listener);\n    }\n\n    public void remove(BrokerClientListener listener) {\n        this.listeners.remove(listener);\n    }\n\n    public void clear() {\n        this.listeners.clear();\n    }\n\n    public void forEach(Consumer<BrokerClientListener> consumer) {\n        this.listeners.forEach(consumer);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/listener/ConnectionBeforeListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.listener;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\n\n/**\n * 连接到 Broker（游戏网关）前的监听\n *\n * @author 渔民小镇\n * @date 2023-12-14\n */\ninterface ConnectionBeforeListener {\n    /**\n     * 将当前逻辑服注册到 Broker（游戏网关）之前的回调\n     * <pre>\n     *     如果有特殊业务的，开发者可以在此方法中给 BrokerClient、BrokerClientModuleMessage 增加一些其他的附加信息\n     *\n     *     this.barSkeleton.getRunners().onStart();\n     *     this.registerBefore()\n     *     ...\n     *     registerToBroker()\n     * </pre>\n     *\n     * @param moduleMessage 当前逻辑服信息\n     * @param client        当前逻辑服\n     * @see BrokerClient#init()\n     */\n    default void registerBefore(BrokerClientModuleMessage moduleMessage, BrokerClient client) {\n\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/listener/LineListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.listener;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\n\n/**\n * 钩子方法，逻辑服上线、下线监听回调\n * <pre>\n *     逻辑服指是的游戏对外服和游戏逻辑服\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-12-14\n */\ninterface LineListener {\n    /**\n     * 其他游戏对外服上线监听\n     * <pre>\n     *     已经在线上的，或者有新上线的游戏对外服都会触发此方法\n     * </pre>\n     *\n     * @param otherModuleMessage 游戏对外服\n     * @param client             当前逻辑服\n     */\n    default void onlineExternal(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n    }\n\n    /**\n     * 其他游戏对外服下线监听\n     *\n     * @param otherModuleMessage 下线的游戏对外服\n     * @param client             当前逻辑服\n     */\n    default void offlineExternal(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n    }\n\n    /**\n     * 其他游戏逻辑服在线监听\n     * <pre>\n     *     已经在线上的，或者有新上线的游戏逻辑服都会触发此方法\n     * </pre>\n     *\n     * @param otherModuleMessage 在线的其他游戏逻辑服\n     * @param client             当前逻辑服\n     */\n    default void onlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n    }\n\n    /**\n     * 其他游戏逻辑服下线监听\n     *\n     * @param otherModuleMessage 下线的游戏逻辑服\n     * @param client             当前逻辑服\n     */\n    default void offlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/listener/SimplePrintBrokerClientListener.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.listener;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClient;\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 打印其他进程逻辑服的上线与下线信息\n *\n * @author 渔民小镇\n * @date 2024-08-19\n * @since 21.15\n */\n@Slf4j\npublic final class SimplePrintBrokerClientListener implements BrokerClientListener {\n    @Override\n    public void onlineExternal(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        if (client.getWithNo() == otherModuleMessage.getWithNo()) {\n            return;\n        }\n\n        // 其他游戏对外服上线监听。已经在线上的，或者有新上线的游戏对外服都会触发此方法。\n        log.info(\"【上线监听】其他进程的游戏对外服信息 {}\", otherModuleMessage);\n    }\n\n    @Override\n    public void offlineExternal(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        if (client.getWithNo() == otherModuleMessage.getWithNo()) {\n            return;\n        }\n\n        // 其他游戏对外服下线监听\n        log.info(\"【下线监听】其他进程的游戏对外服信息 {}\", otherModuleMessage);\n    }\n\n    @Override\n    public void onlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        if (client.getWithNo() == otherModuleMessage.getWithNo()) {\n            return;\n        }\n\n        // 其他游戏逻辑服在线监听。已经在线上的，或者有新上线的游戏逻辑服都会触发此方法\n        log.info(\"【上线监听】其他进程的游戏逻辑服信息 {}\", otherModuleMessage);\n    }\n\n    @Override\n    public void offlineLogic(BrokerClientModuleMessage otherModuleMessage, BrokerClient client) {\n        if (client.getWithNo() == otherModuleMessage.getWithNo()) {\n            return;\n        }\n\n        // 其他游戏逻辑服下线监听\n        log.info(\"【下线监听】其他进程的游戏逻辑服信息 {}\", otherModuleMessage);\n    }\n\n    private SimplePrintBrokerClientListener() {\n    }\n\n    public static SimplePrintBrokerClientListener me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final SimplePrintBrokerClientListener ME = new SimplePrintBrokerClientListener();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/pulse/PulseSignalRequestUserProcessor.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.pulse;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.pulse.core.consumer.PulseConsumers;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalRequest;\nimport com.iohao.game.bolt.broker.core.aware.PulseConsumerAware;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 脉冲信号请求接收\n *\n * @author 渔民小镇\n * @date 2023-04-20\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class PulseSignalRequestUserProcessor extends AbstractAsyncUserProcessor<PulseSignalRequest>\n        implements PulseConsumerAware {\n\n    PulseConsumers pulseConsumers;\n\n    @Override\n    public void setPulseConsumers(PulseConsumers pulseConsumers) {\n        this.pulseConsumers = pulseConsumers;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, PulseSignalRequest request) {\n        // 脉冲消费者接收脉冲发射器发送过来的脉冲信号\n        pulseConsumers.acceptPulseSign(request);\n    }\n\n    @Override\n    public String interest() {\n        return PulseSignalRequest.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/processor/pulse/PulseSignalResponseUserProcessor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.common.processor.pulse;\n\nimport com.alipay.remoting.AsyncContext;\nimport com.alipay.remoting.BizContext;\nimport com.iohao.game.action.skeleton.pulse.core.producer.PulseProducers;\nimport com.iohao.game.action.skeleton.pulse.message.PulseSignalResponse;\nimport com.iohao.game.bolt.broker.core.aware.PulseProducerAware;\nimport com.iohao.game.bolt.broker.core.common.AbstractAsyncUserProcessor;\n\n/**\n * 脉冲信号响应接收\n *\n * @author 渔民小镇\n * @date 2023-04-22\n */\npublic class PulseSignalResponseUserProcessor extends AbstractAsyncUserProcessor<PulseSignalResponse>\n        implements PulseProducerAware {\n    PulseProducers pulseProducers;\n\n    @Override\n    public void setPulseProducers(PulseProducers pulseProducers) {\n        this.pulseProducers = pulseProducers;\n    }\n\n    @Override\n    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, PulseSignalResponse response) {\n        this.pulseProducers.acceptPulseSign(response);\n    }\n\n    @Override\n    public String interest() {\n        return PulseSignalResponse.class.getName();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/kit/HessianKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.kit;\n\nimport com.alipay.remoting.exception.CodecException;\nimport com.alipay.remoting.serialization.HessianSerializer;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-18\n */\n@UtilityClass\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic class HessianKit {\n    final HessianSerializer hessianSerializer = new HessianSerializer();\n\n    public byte[] serialize(Object obj) {\n        try {\n            return hessianSerializer.serialize(obj);\n        } catch (CodecException e) {\n            log.error(e.getMessage(), e);\n            return null;\n        }\n    }\n\n    public <T> T deserialize(byte[] data, Class<T> classOfT) {\n        try {\n            return hessianSerializer.deserialize(data, \"\");\n        } catch (CodecException e) {\n            log.error(e.getMessage(), e);\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/loadbalance/ElementSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.loadbalance;\n\nimport java.util.List;\nimport java.util.function.Supplier;\n\n/**\n * 元素选择器\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\npublic interface ElementSelector<T> extends Supplier<T> {\n    /**\n     * 得到下一个元素\n     *\n     * @return t\n     */\n    T next();\n\n    /**\n     * create default ElementSelectorImpl\n     *\n     * @param elements elements\n     * @param <T>      t\n     * @return ElementSelector\n     * @since 21.19\n     */\n    static <T> ElementSelector<T> of(List<T> elements) {\n        return new RingElementSelector<>(elements);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/loadbalance/ElementSelectorFactory.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.loadbalance;\n\nimport java.util.List;\n\n/**\n * 元素选择器生产工厂\n *\n * @author 渔民小镇\n * @date 2022-08-15\n */\npublic interface ElementSelectorFactory<T> {\n    /**\n     * 创建元素选择器\n     *\n     * @param list element list\n     * @return 元素选择器\n     */\n    ElementSelector<T> createElementSelector(List<T> list);\n}\n\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/loadbalance/RandomElementSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.loadbalance;\n\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n * 随机的元素选择器\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class RandomElementSelector<T> implements ElementSelector<T> {\n    final List<T> elements;\n    final int size;\n\n    public RandomElementSelector(List<T> elements) {\n        this.elements = elements;\n        this.size = elements.size();\n    }\n\n    @Override\n    public T next() {\n\n        if (size > 1) {\n\n            ThreadLocalRandom random = ThreadLocalRandom.current();\n            T element = elements.get(random.nextInt(size));\n\n            if (Objects.isNull(element)) {\n                element = elements.getFirst();\n            }\n\n            return element;\n\n        } else if (size == 1) {\n            return elements.getFirst();\n        }\n\n        return null;\n    }\n\n    @Override\n    public T get() {\n        T next = next();\n\n        if (Objects.isNull(next)) {\n            ThrowKit.ofNullPointerException(\"RandomSelector next is null!\");\n        }\n\n        return next;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/loadbalance/RingElementSelector.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.loadbalance;\n\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * RingElementSelector\n *\n * @author 渔民小镇\n * @date 2024-10-19\n * @since 21.19\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class RingElementSelector<T> implements ElementSelector<T> {\n    final List<T> elements;\n    final int size;\n    final AtomicLong counter = new AtomicLong();\n\n    public RingElementSelector(List<T> elements) {\n        this.elements = elements;\n        this.size = elements.size();\n    }\n\n    @Override\n    public T next() {\n        return switch (size) {\n            case 0 -> null;\n            case 1 -> elements.getFirst();\n            default -> elements.get((int) (counter.getAndIncrement() % size));\n        };\n    }\n\n    @Override\n    public T get() {\n        T next = next();\n\n        if (Objects.isNull(next)) {\n            ThrowKit.ofNullPointerException(\"LoopElementSelector next is null\");\n        }\n\n        return next;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BroadcastMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.Collection;\n\n/**\n * 广播消息\n *\n * @author 渔民小镇\n * @date 2022-03-10\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BroadcastMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -8781053474740658678L;\n\n    /** 广播的消息 */\n    ResponseMessage responseMessage;\n    /** 接收广播的用户列表 */\n    Collection<Long> userIdList;\n    /** true 给全体广播 */\n    boolean broadcastAll;\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BroadcastOrderMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\n/**\n * 广播消息，有顺序的\n *\n * @author 渔民小镇\n * @date 2022-07-14\n */\npublic class BroadcastOrderMessage extends BroadcastMessage {\n\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BrokerClientItemConnectMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * bolt RpcClient.startup 后，需要发送消息才会建立连接\n * <pre>\n *     这里发送一个空消息\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-16\n */\npublic class BrokerClientItemConnectMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 1148652635062833923L;\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BrokerClientModuleMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport com.iohao.game.bolt.broker.core.client.BrokerClientType;\nimport com.iohao.game.common.kit.HashKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.ToString;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 模块信息 （游戏服的信息、逻辑服）\n * <pre>\n *     一个逻辑服表示一个模块\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-05-14\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClientModuleMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -1570849960266785141L;\n    final Map<String, Object> header = new HashMap<>();\n    /** 模块名 */\n    String name;\n    /**\n     * 逻辑服标签 （tag 相当于归类）\n     * <pre>\n     *     用于逻辑服的归类\n     *     假设逻辑服： 战斗逻辑服 启动了两台或以上，为了得到启动连接的逻辑服，我们可以通过 tag 在后台查找\n     *     相同的逻辑服一定要用相同的 tag\n     *\n     *     注意，如果没设置这个值，会使用 this.name 的值\n     * </pre>\n     */\n    String tag;\n    /** 逻辑服状态 */\n    int status;\n    int withNo;\n    /** 逻辑服类型 */\n    BrokerClientType brokerClientType = BrokerClientType.LOGIC;\n    /** 服务器唯一标识 */\n    String id;\n    /** 服务器唯一标识 hash */\n    int idHash;\n    /** 逻辑服地址 */\n    String address;\n    @ToString.Exclude\n    List<Integer> cmdMergeList;\n    /** 模拟的同进程 pid */\n    String ioGamePid;\n\n    public BrokerClientModuleMessage setId(String id) {\n        Objects.requireNonNull(id);\n        this.id = id;\n        this.idHash = HashKit.hash32(id);\n        return this;\n    }\n\n    public void addHeader(String name, Object data) {\n        this.header.put(name, data);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getHeader(String name) {\n        return (T) this.header.get(name);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof BrokerClientModuleMessage that)) {\n            return false;\n        }\n\n        return Objects.equals(id, that.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return id.hashCode();\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BrokerClientOfflineMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serializable;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClientOfflineMessage implements Serializable {\n    BrokerClientModuleMessage moduleMessage;\n\n    public static BrokerClientOfflineMessage of(BrokerClientModuleMessage moduleMessage) {\n        BrokerClientOfflineMessage offlineMessage = new BrokerClientOfflineMessage();\n        offlineMessage.moduleMessage = moduleMessage;\n        return offlineMessage;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BrokerClientOnlineMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serializable;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-14\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClientOnlineMessage implements Serializable {\n    BrokerClientModuleMessage moduleMessage;\n\n    public static BrokerClientOnlineMessage of(BrokerClientModuleMessage moduleMessage) {\n        BrokerClientOnlineMessage onlineMessage = new BrokerClientOnlineMessage();\n        onlineMessage.moduleMessage = moduleMessage;\n        return onlineMessage;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BrokerClusterMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport lombok.AccessLevel;\nimport lombok.Data;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 通知客户端有 broker 上线或下线\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@Data\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerClusterMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 2753485289174578530L;\n    String name;\n    List<BrokerMessage> brokerMessageList;\n\n    public int count() {\n        if (Objects.isNull(brokerMessageList)) {\n            return 0;\n        }\n\n        return this.brokerMessageList.size();\n    }\n\n    public List<BrokerMessage> getBrokerMessageList() {\n        if (Objects.isNull(this.brokerMessageList)) {\n            return Collections.emptyList();\n        }\n\n        return this.brokerMessageList;\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/BrokerMessage.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport lombok.AccessLevel;\nimport lombok.Data;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 游戏网关信息\n *\n * @author 渔民小镇\n * @date 2022-05-15\n */\n@Data\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class BrokerMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 7360874461048656701L;\n\n    /** broker （游戏网关）的服务器 id */\n    String id;\n    /** broker （游戏网关）地址  格式 ip:port */\n    String address;\n}"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/InnerModuleMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 模块之间的访问\n * <pre>\n *     如： 模块A 访问 模块B 的某个方法，因为只有模块B持有这些数据\n *\n *     如果不需要返回值的，see {@link InnerModuleVoidMessage}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-20\n */\n@Data\npublic class InnerModuleMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -5352732154154036339L;\n    RequestMessage requestMessage;\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/InnerModuleVoidMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * 模块之间的访问\n * <pre>\n *     如： 模块A 访问 模块B 的某个方法，但是不需要任何返回值\n *\n *     如果需要返回值的，see {@link InnerModuleMessage}\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-06-07\n */\n@Data\npublic class InnerModuleVoidMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -5740054570053626336L;\n    RequestMessage requestMessage;\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/message/RequestBrokerClientModuleMessage.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.bolt.broker.core.message;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n/**\n * @author 渔民小镇\n * @date 2022-05-16\n */\n@Setter\n@Getter\npublic class RequestBrokerClientModuleMessage implements Serializable {\n    @Serial\n    private static final long serialVersionUID = -8701320309480192037L;\n    int withNo;\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/NetCommonKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common;\n\nimport com.iohao.game.common.kit.ExecutorKit;\nimport lombok.Getter;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.concurrent.Executor;\n\n/**\n * Broker 、 BrokerClient 之间通信专用工具\n *\n * @author 渔民小镇\n * @date 2023-12-19\n */\n@UtilityClass\npublic class NetCommonKit {\n    @Getter\n    final Executor virtualExecutor = ExecutorKit.newVirtualExecutor(\"ioGame-NetVirtualExecutor\");\n\n    /**\n     * 使用虚拟线程执行任务\n     *\n     * @param command 任务\n     */\n    public void executeVirtual(Runnable command) {\n        virtualExecutor.execute(command);\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/client/Attachment.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common.client;\n\nimport com.iohao.game.action.skeleton.core.flow.UserAttachment;\n\n/**\n * 元信息接口\n * <pre>\n *     注意：\n *     框架默认使用的是 protobuf 编解码，所以建议子类添加 ProtobufClass 注解\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-12-11\n */\npublic interface Attachment extends UserAttachment {\n    /**\n     * get userId\n     *\n     * @return userId\n     */\n    long getUserId();\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/client/ExternalBizCodeCont.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common.client;\n\nimport com.iohao.game.action.skeleton.core.IoGameCommonCoreConfig;\n\n/**\n * <pre>\n *     开发者扩展时，用正数的业务码\n *     框架会从负数开始使用\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-12-11\n */\npublic interface ExternalBizCodeCont {\n    /** 用户（玩家）是否在线，ExistUserExternalBizRegion */\n    int existUser = -1;\n    /** 强制用户（玩家）下线，ForcedOfflineExternalBizRegion */\n    int forcedOffline = -2;\n\n    /** 用户（玩家）的元信息同步，AttachmentExternalBizRegion */\n    int attachment = IoGameCommonCoreConfig.ExternalBizCode.attachment;\n    /** 用户（玩家）在游戏对外服的 HeadMetadata 信息 */\n    int userHeadMetadata = -4;\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/cmd/BrokerClientId.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common.cmd;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-01\n */\npublic record BrokerClientId(int idHash, String id) {\n\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/cmd/CmdRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common.cmd;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-01\n */\npublic interface CmdRegion {\n    void addIdHash(BrokerClientId brokerClientId);\n\n    void removeIdHash(BrokerClientId brokerClientId);\n\n    int endPointLogicServerId(int[] idHashArray);\n\n    boolean hasIdHash();\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/cmd/CmdRegions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common.cmd;\n\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-01\n */\npublic interface CmdRegions {\n    void loading(BrokerClientModuleMessage moduleMessage);\n\n    void unLoading(BrokerClientId brokerClientId);\n\n    boolean existCmdMerge(int cmdMerge);\n\n    int endPointLogicServerId(int cmdMerge, int[] idHashArray);\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/cmd/DefaultCmdRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common.cmd;\n\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-30\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultCmdRegion implements CmdRegion {\n    final Set<BrokerClientId> clientIdSet = new NonBlockingHashSet<>();\n    final Set<Integer> clientIdHashSet = new NonBlockingHashSet<>();\n\n    final int cmdMerge;\n\n    public DefaultCmdRegion(int cmdMerge) {\n        this.cmdMerge = cmdMerge;\n    }\n\n    @Override\n    public void addIdHash(BrokerClientId brokerClientId) {\n        this.clientIdSet.add(brokerClientId);\n        this.clientIdHashSet.add(brokerClientId.idHash());\n    }\n\n    @Override\n    public void removeIdHash(BrokerClientId brokerClientId) {\n        this.clientIdSet.remove(brokerClientId);\n        this.clientIdHashSet.remove(brokerClientId.idHash());\n    }\n\n    @Override\n    public int endPointLogicServerId(int[] idHashArray) {\n        for (int idHash : idHashArray) {\n            if (this.clientIdHashSet.contains(idHash)) {\n                return idHash;\n            }\n        }\n\n        return 0;\n    }\n\n    @Override\n    public boolean hasIdHash() {\n        return !this.clientIdSet.isEmpty();\n    }\n\n    @Override\n    public String toString() {\n        return \"CmdRegion {\" +\n                CmdKit.toString(cmdMerge) +\n                \" -- BrokerClientIdSet : \" + clientIdSet +\n                '}';\n    }\n}\n"
  },
  {
    "path": "net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/cmd/DefaultCmdRegions.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.core.common.cmd;\n\n\nimport com.iohao.game.bolt.broker.core.message.BrokerClientModuleMessage;\nimport com.iohao.game.common.kit.MoreKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\nimport org.jctools.maps.NonBlockingHashSet;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * @author 渔民小镇\n * @date 2023-04-30\n */\n@Slf4j\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class DefaultCmdRegions implements CmdRegions {\n    /**\n     * 路由与区域映射\n     * <pre>\n     *     key : cmdMerge\n     *     value : CmdRegion\n     * </pre>\n     */\n    final Map<Integer, CmdRegion> cmdRegionMap = new NonBlockingHashMap<>();\n\n    /**\n     * 游戏逻辑服与对应的 CmdRegion set\n     * <pre>\n     *     key : 游戏逻辑服 id\n     *     value : CmdRegion set\n     * </pre>\n     */\n    final Map<Integer, Set<CmdRegion>> logicServerCmdRegionMap = new NonBlockingHashMap<>();\n\n    @Override\n    public void loading(BrokerClientModuleMessage moduleMessage) {\n        String id = moduleMessage.getId();\n        int idHash = moduleMessage.getIdHash();\n        BrokerClientId brokerClientId = new BrokerClientId(idHash, id);\n\n        for (Integer cmdMerge : moduleMessage.getCmdMergeList()) {\n            CmdRegion cmdRegion = this.getCmdRegion(cmdMerge);\n            cmdRegion.addIdHash(brokerClientId);\n\n            Set<CmdRegion> cmdRegionSet = this.getCmdRegionSet(idHash);\n            cmdRegionSet.add(cmdRegion);\n        }\n    }\n\n    @Override\n    public void unLoading(BrokerClientId brokerClientId) {\n        int idHash = brokerClientId.idHash();\n        // 只简单的移除 idHash 不销毁对象\n        this.getCmdRegionSet(idHash).forEach(cmdRegion -> cmdRegion.removeIdHash(brokerClientId));\n    }\n\n    @Override\n    public boolean existCmdMerge(int cmdMerge) {\n        CmdRegion cmdRegion = this.cmdRegionMap.get(cmdMerge);\n        // cmdRegion 存在，并且是有游戏逻辑服 id 的\n        return Objects.nonNull(cmdRegion) && cmdRegion.hasIdHash();\n    }\n\n    @Override\n    public int endPointLogicServerId(int cmdMerge, int[] idHashArray) {\n\n        CmdRegion cmdRegion = this.cmdRegionMap.get(cmdMerge);\n        if (Objects.isNull(cmdRegion)) {\n            return 0;\n        }\n\n        return cmdRegion.endPointLogicServerId(idHashArray);\n    }\n\n    private Set<CmdRegion> getCmdRegionSet(int idHash) {\n        Set<CmdRegion> cmdRegionSet = this.logicServerCmdRegionMap.get(idHash);\n\n        // 无锁化\n        if (Objects.isNull(cmdRegionSet)) {\n            Set<CmdRegion> newValue = new NonBlockingHashSet<>();\n            return MoreKit.putIfAbsent(this.logicServerCmdRegionMap, idHash, newValue);\n        }\n\n        return cmdRegionSet;\n    }\n\n    private CmdRegion getCmdRegion(int cmdMerge) {\n        // cmdMerge 与 idHash 关联\n        CmdRegion cmdRegion = this.cmdRegionMap.get(cmdMerge);\n\n        // 无锁化\n        if (Objects.isNull(cmdRegion)) {\n            CmdRegion newValue = new DefaultCmdRegion(cmdMerge);\n            return MoreKit.putIfAbsent(cmdRegionMap, cmdMerge, newValue);\n        }\n\n        return cmdRegion;\n    }\n}\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <packaging>pom</packaging>\n\n    <groupId>com.iohao.game</groupId>\n    <artifactId>ioGame</artifactId>\n    <version>21.34</version>\n    <name>ioGame</name>\n    <description>\n        生产资料公有制。\n        让网络游戏服务器的编程变得轻松简单。\n        ioGame 是一个轻量级的网络编程框架，适用于网络游戏服务器、物联网、内部系统及各种需要长连接的场景。\n    </description>\n    <url>https://iohao.github.io/game</url>\n\n    <modules>\n        <!-- 网络游戏框架 - 业务框架 -->\n        <module>common/common-micro-kit</module>\n        <module>common/common-kit</module>\n        <module>common/common-core</module>\n        <!--数据校验模块-->\n        <module>common/common-validation</module>\n        <!-- 网络游戏框架 - 网络通信 -->\n        <module>net-bolt/bolt-core</module>\n        <!-- 新版游戏对外服 -->\n        <module>external/external-core</module>\n        <module>external/external-netty</module>\n        <module>run-one/run-one-netty</module>\n        <!-- Broker （游戏网关） -->\n        <module>net-bolt/bolt-broker-server</module>\n        <!-- BrokerClient （逻辑服） -->\n        <module>net-bolt/bolt-client</module>\n\n        <!-- 小部件 : 领域事件 -->\n        <module>widget/light-domain-event</module>\n        <!-- 小部件 : 多环境切换 -->\n        <module>widget/light-profile</module>\n        <!-- 小部件 : jprotobuf 增强 -->\n        <module>widget/light-jprotobuf</module>\n        <!-- 小部件 ： 压测&模拟客户端请求 -->\n        <module>widget/light-client</module>\n        <!-- 小部件 : 桌游类、房间类游戏的扩展模块，进一步减少开发实践过程中的工作量 -->\n        <module>widget/light-game-room</module>\n        <module>widget/other-tool</module>\n        <module>widget/generate-code</module>\n    </modules>\n\n    <!--统一管理版本 的一个父 pom-->\n    <properties>\n        <!-- jdk 版本 -->\n        <java.version>21</java.version>\n        <encoding>UTF-8</encoding>\n\n        <!-- 网络库 : https://mvnrepository.com/artifact/io.netty/netty-all -->\n        <netty.version>4.1.122.Final</netty.version>\n        <!--  bolt https://mvnrepository.com/artifact/com.alipay.sofa/bolt  -->\n        <bolt.version>1.6.6</bolt.version>\n        <!-- hessian 格式化 https://mvnrepository.com/artifact/com.caucho/hessian -->\n        <hessian.version>4.0.66</hessian.version>\n        <!--  Jansi 控制台输出彩色文字 https://mvnrepository.com/artifact/org.fusesource.jansi/jansi  -->\n        <jansi.version>2.4.1</jansi.version>\n        <!-- Java文档解析器 https://mvnrepository.com/artifact/com.thoughtworks.qdox/qdox -->\n        <qdox.version>2.1.0</qdox.version>\n\n        <!-- JCTools 是一款对jdk并发数据结构进行增强的并发工具 see http://jctools.github.io/JCTools/ https://mvnrepository.com/artifact/org.jctools/jctools-core -->\n        <jctools-core.version>4.0.5</jctools-core.version>\n\n        <!-- json 解析器 : https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->\n        <fastjson.version>2.0.46</fastjson.version>\n\n        <!--  reflectasm 高性能的反射处理 https://mvnrepository.com/artifact/com.esotericsoftware/reflectasm  -->\n        <reflectasm.version>1.11.9</reflectasm.version>\n\n        <!--  disruptor https://mvnrepository.com/artifact/com.lmax/disruptor -->\n        <disruptor.version>3.4.4</disruptor.version>\n\n        <!-- JSR-380 bean 验证框架 https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->\n        <hibernate-validator.version>8.0.1.Final</hibernate-validator.version>\n        <!-- JSR-380 https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->\n        <jakarta.validation-api.version>3.1.0</jakarta.validation-api.version>\n        <!-- JSR-380 https://mvnrepository.com/artifact/org.glassfish/jakarta.el -->\n        <jakarta.el.version>4.0.2</jakarta.el.version>\n\n        <!-- temp spring boot https://mvnrepository.com/artifact/org.springframework/spring-core -->\n        <spring.version>6.1.12</spring.version>\n\n        <!-- slf4j https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->\n        <slf4j-api.version>2.0.16</slf4j-api.version>\n        <!-- slf4j https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->\n        <logback.version>1.5.18</logback.version>\n        <!-- lombok 消除冗长的 Java 代码 https://mvnrepository.com/artifact/org.projectlombok/lombok -->\n        <lombok.version>1.18.34</lombok.version>\n        <!--  junit https://mvnrepository.com/artifact/junit/junit  -->\n        <junit.version>4.13.2</junit.version>\n\n        <!--\n        jprotobuf是针对Java程序开发一套简易类库，目的是简化java语言对protobuf类库的使用\n        https://github.com/jhunters/jprotobuf/\n        https://mvnrepository.com/artifact/com.baidu/jprotobuf\n        -->\n        <jprotobuf.version>2.4.23</jprotobuf.version>\n        <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->\n        <protobuf-java.version>3.25.8</protobuf-java.version>\n    </properties>\n\n    <dependencies>\n        <!-- lombok 简化 java 代码 -->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <version>${lombok.version}</version>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <version>${slf4j-api.version}</version>\n        </dependency>\n        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-classic</artifactId>\n            <version>${logback.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/junit/junit -->\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>${junit.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!--\n        jprotobuf是针对Java程序开发一套简易类库，目的是简化java语言对protobuf类库的使用\n        https://github.com/jhunters/jprotobuf/\n        https://mvnrepository.com/artifact/com.baidu/jprotobuf\n        -->\n        <dependency>\n            <groupId>com.baidu</groupId>\n            <artifactId>jprotobuf</artifactId>\n            <version>${jprotobuf.version}</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.slf4j</groupId>\n                    <artifactId>slf4j-api</artifactId>\n                </exclusion>\n                <exclusion>\n                    <groupId>com.google.protobuf</groupId>\n                    <artifactId>protobuf-java</artifactId>\n                </exclusion>\n                <exclusion>\n                    <groupId>commons-io</groupId>\n                    <artifactId>commons-io</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->\n        <dependency>\n            <groupId>commons-io</groupId>\n            <artifactId>commons-io</artifactId>\n            <version>2.19.0</version>\n        </dependency>\n        <!-- jprotobuf dependency https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->\n        <dependency>\n            <groupId>com.google.protobuf</groupId>\n            <artifactId>protobuf-java</artifactId>\n            <version>${protobuf-java.version}</version>\n        </dependency>\n\n    </dependencies>\n\n    <issueManagement>\n        <system>Github Issue</system>\n        <url>https://github.com/iohao/ioGame/issues</url>\n    </issueManagement>\n\n    <licenses>\n        <license>\n            <name>GNU Affero General Public License v3.0</name>\n            <url>https://www.gnu.org/licenses/agpl-3.0.txt</url>\n        </license>\n    </licenses>\n\n    <developers>\n        <developer>\n            <name>渔民小镇</name>\n            <id>luoyizhu@gmail.com</id>\n            <email>262610965@qq.com</email>\n        </developer>\n    </developers>\n\n    <scm>\n        <url>https://github.com/iohao/ioGame</url>\n        <connection>https://github.com/iohao/ioGame.git</connection>\n        <developerConnection>https://github.com/iohao</developerConnection>\n    </scm>\n\n    <build>\n        <plugins>\n            <!--\n            mvnd javadoc:aggregate\n            Javadoc 本地生成，文档将生成在 target/site/apidocs 中\n            -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>3.6.3</version>\n                <configuration>\n                    <encoding>UTF-8</encoding>\n                    <charset>UTF-8</charset>\n                    <docencoding>UTF-8</docencoding>\n                    <failOnError>false</failOnError>\n                    <additionalOptions>-Xdoclint:none</additionalOptions>\n                    <tags>\n                        <tag>\n                            <name>date</name>\n                            <placement>a</placement>\n                            <head>日期:</head>\n                        </tag>\n                    </tags>\n                </configuration>\n            </plugin>\n\n            <!--\n            编译插件\n            mvn compile\n            To compile your test sources, you'll do:\n            mvn test-compile\n            -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <!--                <version>3.11.0</version>-->\n                <version>3.8.1</version>\n                <configuration>\n                    <compilerVersion>${java.version}</compilerVersion>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <!-- maven 3.6.2及之后加上编译参数，可以让我们在运行期获取方法参数名称。 -->\n                    <parameters>true</parameters>\n                    <skip>true</skip>\n                    <!-- JDK9+ with module-info.java -->\n                    <annotationProcessorPaths>\n                        <!-- lombok 消除冗长的 Java 代码 -->\n                        <path>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                            <version>${lombok.version}</version>\n                        </path>\n                        <!-- additional annotation processor required as of Lombok 1.18.16 -->\n                        <!-- mapStruct 支持 lombok -->\n                        <path>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok-mapstruct-binding</artifactId>\n                            <version>0.2.0</version>\n                        </path>\n                    </annotationProcessorPaths>\n                </configuration>\n            </plugin>\n\n            <!-- 打包时跳过单元测试 https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-surefire-plugin -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.0.0-M5</version>\n                <configuration>\n                    <skipTests>true</skipTests>\n                </configuration>\n            </plugin>\n\n            <!-- 打包源码 https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-source-plugin -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>3.2.1</version>\n                <configuration>\n                    <attach>true</attach>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>compile</phase>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-jar-plugin -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <version>3.2.2</version>\n                <configuration>\n                    <archive>\n                        <index>true</index>\n                        <manifest>\n                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                            <addBuildEnvironmentEntries>true</addBuildEnvironmentEntries>\n                        </manifest>\n                        <manifestSections>\n                            <manifestSection>\n                                <name>hope</name>\n                                <manifestEntries>\n                                    <id>Public ownership of means of production</id>\n                                </manifestEntries>\n                            </manifestSection>\n                            <manifestSection>\n                                <name>zhu luo yi</name>\n                                <manifestEntries>\n                                    <id>渔民小镇</id>\n                                </manifestEntries>\n                            </manifestSection>\n                        </manifestSections>\n\n                    </archive>\n                </configuration>\n            </plugin>\n\n        </plugins>\n    </build>\n\n    <profiles>\n        <!-- mvn clean deploy -P oss-release -Dmaven.test.skip=true -e -->\n        <profile>\n            <id>oss-release</id>\n\n            <build>\n                <plugins>\n                    <!-- Gpg Signature -->\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-gpg-plugin</artifactId>\n                        <version>3.0.1</version>\n                        <executions>\n                            <execution>\n                                <id>sign-artifacts</id>\n                                <phase>verify</phase>\n                                <goals>\n                                    <goal>sign</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n\n                    <!-- 自动发布 https://mvnrepository.com/artifact/org.sonatype.central/central-publishing-maven-plugin -->\n                    <plugin>\n                        <groupId>org.sonatype.central</groupId>\n                        <artifactId>central-publishing-maven-plugin</artifactId>\n                        <version>0.8.0</version>\n                        <extensions>true</extensions>\n                        <configuration>\n                            <publishingServerId>central</publishingServerId>\n                            <autoPublish>true</autoPublish>\n                            <waitUntil>published</waitUntil>\n                        </configuration>\n                    </plugin>\n\n                    <!-- Javadoc -->\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-javadoc-plugin</artifactId>\n                        <version>3.5.0</version>\n                        <executions>\n                            <execution>\n                                <phase>package</phase>\n                                <goals>\n                                    <goal>jar</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                        <configuration>\n                            <javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>\n                            <doclint>none</doclint>\n                        </configuration>\n                    </plugin>\n\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n    <distributionManagement>\n        <repository>\n            <!-- 这里的id要与 maven setting.xml 中 server 的 id 一致 -->\n            <id>oss-ioGame</id>\n            <name>Nexus Release Repository</name>\n            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>\n            <uniqueVersion>true</uniqueVersion>\n        </repository>\n    </distributionManagement>\n</project>\n"
  },
  {
    "path": "run-one/run-one-netty/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.iohao.game</groupId>\n        <artifactId>ioGame</artifactId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n\n    <artifactId>run-one-netty</artifactId>\n    <name>run-one-netty for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>external-netty</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>bolt-broker-server</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "run-one/run-one-netty/src/main/java/com/iohao/game/external/core/netty/simple/InternalRunOne.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.simple;\n\nimport com.iohao.game.bolt.broker.client.AbstractBrokerClientStartup;\nimport com.iohao.game.bolt.broker.client.BrokerClientApplication;\nimport com.iohao.game.bolt.broker.core.GroupWith;\nimport com.iohao.game.common.kit.HashKit;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.external.core.ExternalServer;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-06\n */\n@Slf4j\n@Setter(AccessLevel.PACKAGE)\nclass InternalRunOne {\n    final ExecutorService executorService = TaskKit.getCacheExecutor();\n    final int withNo = HashKit.hash32(UUID.randomUUID().toString());\n    /** 游戏对外服列表 */\n    List<ExternalServer> externalServerList;\n    /** 逻辑服 */\n    List<AbstractBrokerClientStartup> logicServerList;\n    boolean openWithNo = true;\n\n    void execute(Runnable command) {\n        this.executorService.execute(command);\n    }\n\n    void startupLogic() {\n        // 启动游戏逻辑服\n        if (Objects.nonNull(this.logicServerList)) {\n            this.logicServerList.forEach(logicServer -> {\n                logicServer.setWithNo(this.getWithNo());\n                this.executorService.execute(() -> BrokerClientApplication.start(logicServer));\n            });\n        }\n\n        // 启动游戏对外服\n        if (Objects.nonNull(this.externalServerList)) {\n            this.externalServerList.forEach(externalServer -> {\n                if (externalServer instanceof GroupWith groupWith) {\n                    groupWith.setWithNo(this.getWithNo());\n                }\n\n                this.executorService.execute(externalServer::startup);\n            });\n        }\n\n        try {\n            TimeUnit.SECONDS.sleep(1);\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 添加游戏对外服\n     *\n     * @param externalServer 游戏对外服\n     */\n    void setExternalServer(ExternalServer externalServer) {\n\n        if (Objects.isNull(externalServer)) {\n            return;\n        }\n\n        if (Objects.isNull(this.externalServerList)) {\n            this.externalServerList = new ArrayList<>();\n        }\n\n        this.externalServerList.add(externalServer);\n    }\n\n    int getWithNo() {\n        return this.openWithNo ? this.withNo : 0;\n    }\n}\n"
  },
  {
    "path": "run-one/run-one-netty/src/main/java/com/iohao/game/external/core/netty/simple/NettyClusterSimpleHelper.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.simple;\n\nimport com.iohao.game.bolt.broker.client.AbstractBrokerClientStartup;\nimport com.iohao.game.bolt.broker.cluster.BrokerCluster;\nimport com.iohao.game.bolt.broker.cluster.BrokerClusterManagerBuilder;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.bolt.broker.server.BrokerServerBuilder;\nimport com.iohao.game.external.core.ExternalServer;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.netty.kit.ExternalServerCreateKit;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.List;\n\n/**\n * 简单的快速启动工具： 对外服、游戏网关集群(3个节点)、游戏逻辑服\n * <pre>\n *     注意：\n *          这个工具只适合单机的开发或本地一体化的开发，对于生产不适合。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-04-28\n */\n@UtilityClass\npublic class NettyClusterSimpleHelper {\n\n\n    /**\n     * 简单的快速启动\n     * <pre>\n     *     快速启动:\n     *          对外服 tcp 方式连接\n     *          游戏网关集群\n     *          逻辑服\n     *\n     *      包括游戏业务文档的生成\n     * </pre>\n     *\n     * @param externalPort 游戏对外服端口\n     * @param logicList    逻辑服列表\n     */\n    public void runTcp(int externalPort, List<AbstractBrokerClientStartup> logicList) {\n        runInternal(externalPort, logicList, ExternalJoinEnum.TCP);\n    }\n\n    /**\n     * 简单的快速启动\n     * <pre>\n     *     快速启动:\n     *          对外服 websocket 方式连接\n     *          游戏网关集群\n     *          逻辑服\n     *\n     *      包括游戏业务文档的生成\n     * </pre>\n     *\n     * @param externalPort 游戏对外服端口\n     * @param logicList    逻辑服列表\n     */\n    public void run(int externalPort, List<AbstractBrokerClientStartup> logicList) {\n        runInternal(externalPort, logicList, ExternalJoinEnum.WEBSOCKET);\n    }\n\n    private void runInternal(int externalPort, List<AbstractBrokerClientStartup> logicList, ExternalJoinEnum externalJoinEnum) {\n        // 对外服\n        ExternalServer externalServer = ExternalServerCreateKit.createExternalServer(externalPort, externalJoinEnum);\n\n        // 集群简单的启动器\n        new NettyClusterSimpleRunOne()\n                // 对外服\n                .setExternalServer(externalServer)\n                // 逻辑服列表\n                .setLogicServerList(logicList)\n                // 启动 对外服、网关、逻辑服\n                .startup();\n    }\n\n    public BrokerServer createBrokerServer(List<String> seedAddress, int gossipListenPort, int port) {\n        // 集群的管理 构建器\n        BrokerClusterManagerBuilder brokerClusterManagerBuilder = BrokerCluster.newBrokerClusterManagerBuilder()\n                // Gossip listen port 监听端口\n                .gossipListenPort(gossipListenPort)\n                // 种子节点地址\n                .seedAddress(seedAddress);\n\n        // Bolt Broker Server 构建器\n        BrokerServerBuilder brokerServerBuilder = BrokerServer.newBuilder()\n                // broker 端口（游戏网关端口）\n                .port(port)\n                // 集群的管理构建器，如果不设置，表示不需要集群\n                .brokerClusterManagerBuilder(brokerClusterManagerBuilder);\n\n        // Bolt Broker Server （游戏网关）\n        return brokerServerBuilder.build();\n    }\n}\n"
  },
  {
    "path": "run-one/run-one-netty/src/main/java/com/iohao/game/external/core/netty/simple/NettyClusterSimpleRunOne.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.simple;\n\nimport com.iohao.game.action.skeleton.core.ActionCommandRegionGlobalCheckKit;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.client.AbstractBrokerClientStartup;\nimport com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.external.core.ExternalServer;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * （集群相关的）集群简单的启动器： 对外服、游戏网关（3个节点）、逻辑服\n * 谐音:拳皇98中的 round one ready go!\n * <pre>\n *     注意：\n *          这个工具只适合单机的开发或本地一体化的开发, 对于分布式不适合。\n *\n * </pre>\n * 集群介绍\n * <pre>\n *     格式： ip:port\n *\n *     -- 生产环境的建议 --\n *     注意，在生产上建议一台物理机配置一个 broker （游戏网关）\n *     一个 broker 就是一个节点\n *     比如配置三台机器，端口可以使用同样的端口，假设三台机器的 ip 分别是:\n *     192.168.1.10:30056\n *     192.168.1.11:30056\n *     192.168.1.12:30056\n *\n *     -- 为了方便演示 --\n *     这里配置写死是方便在一台机器上启动集群\n *     但是同一台机器启动多个 broker 来实现集群就要使用不同的端口，因为《端口被占用，不能相同》\n *     所以这里的配置是：\n *     127.0.0.1:30056\n *     127.0.0.1:30057\n *     127.0.0.1:30058\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-04-28\n */\n@Slf4j\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class NettyClusterSimpleRunOne {\n    final InternalRunOne runOne = new InternalRunOne();\n\n    /** true 在本地启动 broker （游戏网关）集群 */\n    boolean runBrokerServerCluster = true;\n\n    /**\n     * 简单的快速启动\n     * <pre>\n     *     快速启动:\n     *          对外服\n     *          游戏网关集群\n     *          逻辑服\n     *\n     *      注意1：\n     *          方法会启动 3 个游戏网关来演示集群，端口分别是：30056、30057、30058\n     *\n     *      注意2：\n     *          因为 broker （游戏网关） 集群是无中心节点的，所以逻辑服可以选择与任意一台网关建立连接，\n     *          逻辑服内部会自动的与集群其他节点建立连接\n     * </pre>\n     */\n    public void startup() {\n        banner();\n\n        // 启动网关集群（3个节点）\n        if (this.runBrokerServerCluster) {\n            this.clusterBrokerServer();\n        }\n\n        try {\n            // 暂停 0.5 秒，让本地网关集群先启动完成\n            TimeUnit.MILLISECONDS.sleep(500);\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n\n        // 启动逻辑服、对外服\n        this.runOne.startupLogic();\n\n        // 全局重复路由检测工具\n        ActionCommandRegionGlobalCheckKit.checkGlobalExistSubCmd();\n    }\n\n    /**\n     * set 游戏逻辑服列表\n     *\n     * @param logicServerList 游戏逻辑服列表\n     * @return this\n     */\n    public NettyClusterSimpleRunOne setLogicServerList(List<AbstractBrokerClientStartup> logicServerList) {\n        this.runOne.setLogicServerList(logicServerList);\n        return this;\n    }\n\n    /**\n     * 添加游戏对外服\n     *\n     * @param externalServer 游戏对外服\n     * @return this\n     */\n    public NettyClusterSimpleRunOne setExternalServer(ExternalServer externalServer) {\n        this.runOne.setExternalServer(externalServer);\n        return this;\n    }\n\n    /**\n     * set 游戏对外服列表\n     *\n     * @param externalServerList 游戏对外服列表\n     * @return this\n     */\n    public NettyClusterSimpleRunOne setExternalServerList(List<ExternalServer> externalServerList) {\n        this.runOne.setExternalServerList(externalServerList);\n        return this;\n    }\n\n    public NettyClusterSimpleRunOne setOpenWithNo(boolean openWithNo) {\n        this.runOne.setOpenWithNo(openWithNo);\n        return this;\n    }\n\n    /**\n     * 禁用 broker （游戏网关）集群\n     * <pre>\n     *     本地不启动游戏网关集群\n     *     如果公司团队开发中，可以把 broker （游戏网关）集群，部署在其他机器上\n     *     而本机启动的逻辑服连接到这些游戏网关集群上，这样就可以共用游戏网关集群，不用每次在本机启动集群\n     *     这样调试起来也方便\n     * </pre>\n     *\n     * @return this\n     */\n    public NettyClusterSimpleRunOne disableBrokerServerCluster() {\n        this.runBrokerServerCluster = false;\n        return this;\n    }\n\n    private void clusterBrokerServer() {\n\n        /*\n         * 种子节点地址\n         * <pre>\n         *     格式： ip:port\n         *\n         *     -- 生产环境的建议 --\n         *     注意，在生产上建议一台物理机配置一个 broker （游戏网关）\n         *     一个 broker 就是一个节点\n         *     比如配置三台机器，端口可以使用同样的端口，假设三台机器的 ip 分别是:\n         *     192.168.1.10:30056\n         *     192.168.1.11:30056\n         *     192.168.1.12:30056\n         *\n         *     -- 为了方便演示 --\n         *     这里配置写死是方便在一台机器上启动集群\n         *     但是同一台机器启动多个 broker 来实现集群就要使用不同的端口，因为《端口被占用，不能相同》\n         *     所以这里的配置是：\n         *     127.0.0.1:30056\n         *     127.0.0.1:30057\n         *     127.0.0.1:30058\n         * </pre>\n         */\n        List<String> seedAddress = List.of(\n                \"127.0.0.1:30056\",\n                \"127.0.0.1:30057\",\n                \"127.0.0.1:30058\"\n        );\n\n        // Gossip listen port 监听端口\n        int gossipListenPort = IoGameGlobalConfig.gossipListenPort;\n        // broker 端口（游戏网关端口）\n        int port = IoGameGlobalConfig.brokerPort;\n        // ---- 第1台 broker ----\n        this.createBrokerServer(seedAddress, gossipListenPort, port);\n\n        // Gossip listen port 监听端口\n        gossipListenPort = 30057;\n        // broker 端口（游戏网关端口）\n        port = 10201;\n        //  ---- 第2台 broker ----\n        this.createBrokerServer(seedAddress, gossipListenPort, port);\n\n        // Gossip listen port 监听端口\n        gossipListenPort = 30058;\n        // broker 端口（游戏网关端口）\n        port = 10202;\n        //  ---- 第3台 broker ----\n        this.createBrokerServer(seedAddress, gossipListenPort, port);\n    }\n\n    private void createBrokerServer(List<String> seedAddress, int gossipListenPort, int port) {\n        BrokerServer brokerServer = NettyClusterSimpleHelper.createBrokerServer(seedAddress, gossipListenPort, port);\n\n        // 启动游戏网关\n        brokerServer.setWithNo(this.runOne.getWithNo());\n        this.runOne.execute(brokerServer::startup);\n    }\n\n    private void banner() {\n\n        int num = 0;\n\n        if (Objects.nonNull(this.runOne.logicServerList)) {\n            num += this.runOne.logicServerList.size();\n        }\n\n        if (Objects.nonNull(this.runOne.externalServerList)) {\n            num += this.runOne.externalServerList.size();\n        }\n\n        if (this.runBrokerServerCluster) {\n            num += 3;\n        }\n\n        IoGameBanner.me().initCountDownLatch(num);\n        IoGameBanner.render();\n    }\n}\n"
  },
  {
    "path": "run-one/run-one-netty/src/main/java/com/iohao/game/external/core/netty/simple/NettyRunOne.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.simple;\n\nimport com.iohao.game.action.skeleton.core.ActionCommandRegionGlobalCheckKit;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.bolt.broker.client.AbstractBrokerClientStartup;\nimport com.iohao.game.bolt.broker.server.BrokerServer;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.core.ExternalServer;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 简单的启动器： 游戏对外服、游戏网关、游戏逻辑服\n * 谐音：拳皇98 中的 round one ready go!\n * <pre>\n *     注意：\n *          这个工具只适合单机的开发或本地一体化的开发, 对于分布式不适合。\n *\n *          当然如果打算开发单体应用，这种方式是很合适的。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic final class NettyRunOne {\n    @Getter(AccessLevel.PRIVATE)\n    final InternalRunOne runOne = new InternalRunOne();\n    /** broker 游戏网关 */\n    BrokerServer brokerServer;\n    /** true 在本地启动 broker （游戏网关） */\n    boolean runBrokerServer = true;\n\n    /**\n     * 简单的快速启动，\n     * <pre>\n     *     游戏对外服、Broker（游戏网关）、游戏逻辑服这三部分，在一个进程中使用内存通信。\n     *     【单体应用；在开发分布式时，调试更加方便】\n     * </pre>\n     */\n    public void startup() {\n        this.banner();\n\n        // 启动 Broker（游戏网关）\n        if (this.runBrokerServer) {\n\n            if (Objects.isNull(this.brokerServer)) {\n                this.brokerServer = BrokerServer.newBuilder().build();\n            }\n\n            this.brokerServer.setWithNo(this.runOne.getWithNo());\n            this.runOne.execute(this.brokerServer::startup);\n        }\n\n        this.runOne.startupLogic();\n\n        // 全局重复路由检测工具\n        ActionCommandRegionGlobalCheckKit.checkGlobalExistSubCmd();\n    }\n\n    /**\n     * set 游戏逻辑服列表\n     *\n     * @param logicServerList 游戏逻辑服列表\n     * @return this\n     */\n    public NettyRunOne setLogicServerList(List<AbstractBrokerClientStartup> logicServerList) {\n        this.runOne.setLogicServerList(logicServerList);\n        return this;\n    }\n\n    /**\n     * 添加游戏对外服\n     *\n     * @param externalServer 游戏对外服\n     * @return this\n     */\n    public NettyRunOne setExternalServer(ExternalServer externalServer) {\n        this.runOne.setExternalServer(externalServer);\n        return this;\n    }\n\n    /**\n     * set 游戏对外服列表\n     *\n     * @param externalServerList 游戏对外服列表\n     * @return this\n     */\n    public NettyRunOne setExternalServerList(List<ExternalServer> externalServerList) {\n        this.runOne.setExternalServerList(externalServerList);\n        return this;\n    }\n\n    public NettyRunOne setOpenWithNo(boolean openWithNo) {\n        this.runOne.setOpenWithNo(openWithNo);\n        return this;\n    }\n\n    private void banner() {\n\n        int num = 0;\n\n        if (Objects.nonNull(this.runOne.logicServerList)) {\n            num += this.runOne.logicServerList.size();\n        }\n\n        if (Objects.nonNull(this.runOne.externalServerList)) {\n            num += this.runOne.externalServerList.size();\n        }\n\n        if (this.runBrokerServer) {\n            num++;\n        }\n\n        IoGameBanner.me().initCountDownLatch(num);\n\n        IoGameBanner.render();\n    }\n}\n"
  },
  {
    "path": "run-one/run-one-netty/src/main/java/com/iohao/game/external/core/netty/simple/NettySimpleHelper.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.core.netty.simple;\n\nimport com.iohao.game.bolt.broker.client.AbstractBrokerClientStartup;\nimport com.iohao.game.external.core.ExternalServer;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.netty.kit.ExternalServerCreateKit;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2023-02-19\n */\n@UtilityClass\npublic class NettySimpleHelper {\n\n    /**\n     * 简单的快速启动\n     * <pre>\n     *     快速启动:\n     *          对外服 websocket 方式连接\n     *          网关服 默认端口 10200\n     *          逻辑服\n     * </pre>\n     *\n     * @param externalPort 游戏对外服端口\n     * @param logicList    逻辑服列表\n     */\n    public void run(int externalPort, List<AbstractBrokerClientStartup> logicList) {\n        run(externalPort, logicList, ExternalJoinEnum.WEBSOCKET);\n    }\n\n    /**\n     * 简单的快速启动\n     * <pre>\n     *     快速启动:\n     *          对外服 tcp 方式连接\n     *          网关服 默认端口 10200\n     *          逻辑服\n     *\n     *      包括游戏业务文档的生成\n     *\n     *      <a href=\"https://iohao.github.io/game/docs/external/external_tcp\">tcp 连接示例文档</a>\n     * </pre>\n     *\n     * @param externalPort 游戏对外服端口\n     * @param logicList    逻辑服列表\n     */\n    public void runTcp(int externalPort, List<AbstractBrokerClientStartup> logicList) {\n        run(externalPort, logicList, ExternalJoinEnum.TCP);\n    }\n\n    public void run(int externalPort\n            , List<AbstractBrokerClientStartup> logicList\n            , ExternalJoinEnum externalJoinEnum) {\n\n        // netty - 游戏对外服\n        ExternalServer externalServer = ExternalServerCreateKit.createExternalServer(externalPort, externalJoinEnum);\n\n        // 简单的启动器\n        new NettyRunOne()\n                // 游戏对外服\n                .setExternalServer(externalServer)\n                // 游戏逻辑服列表\n                .setLogicServerList(logicList)\n                // 启动 游戏对外服、游戏网关服、游戏逻辑服\n                .startup();\n    }\n}\n"
  },
  {
    "path": "widget/generate-code/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.iohao.game</groupId>\n        <artifactId>ioGame</artifactId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n\n    <artifactId>generate-code</artifactId>\n    <name>generate-code for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>common-core</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>light-jprotobuf</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/com.ibeetl/beetl -->\n        <dependency>\n            <groupId>com.ibeetl</groupId>\n            <artifactId>beetl</artifactId>\n            <version>3.17.0.RELEASE</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "widget/generate-code/src/main/java/com/iohao/game/action/skeleton/core/doc/CsharpDocumentGenerate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.beetl.core.Template;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * Generate C# code, such as broadcast, error code, action\n *\n * @author 渔民小镇\n * @date 2024-11-15\n * @since 21.20\n */\n@Slf4j\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class CsharpDocumentGenerate extends AbstractDocumentGenerate {\n    /** c# namespace that generate files */\n    String namespace = \"IoGame.Gen\";\n    @Setter(AccessLevel.PRIVATE)\n    CsharpAnalyseImport analyseImport;\n\n    public CsharpDocumentGenerate() {\n        this.typeMappingDocument = new CSharpTypeMappingDocument(this);\n    }\n\n    @Override\n    public void generate(IoGameDocument ioGameDocument) {\n        Objects.requireNonNull(this.path);\n        InternalProtoClassKit.analyseProtoClass(ioGameDocument);\n        Map<Class<?>, ProtoFileMergeClass> protoClassMap = InternalProtoClassKit.protoClassMap;\n        this.analyseImport = new CsharpAnalyseImport(protoClassMap.values());\n\n        this.generateAction(ioGameDocument);\n        this.generateBroadcast(ioGameDocument);\n        this.generateErrorCode(ioGameDocument);\n\n        log.info(\"CSharpDocumentGenerate success: {}\", this.path);\n    }\n\n    @Override\n    protected void generateErrorCode(IoGameDocument ioGameDocument) {\n        Template template = ofTemplate(\"game_code.txt\");\n        template.binding(\"namespace\", this.namespace);\n\n        new GameCodeGenerate()\n                .setIoGameDocument(ioGameDocument)\n                .setInternalErrorCode(this.internalErrorCode)\n                .setTemplate(template)\n                .setFilePath(this.path)\n                .setFileSuffix(\".cs\")\n                .generate();\n    }\n\n    @Override\n    protected void generateAction(IoGameDocument ioGameDocument) {\n        List<ActionDocument> actionDocumentList = DocumentAnalyseKit.analyseActionDocument(ioGameDocument, typeMappingDocument);\n\n        actionDocumentList.forEach(actionDocument -> {\n            Template template = ofTemplate(\"action.txt\");\n            template.binding(\"namespace\", this.namespace);\n            template.binding(\"publicActionCmdName\", this.publicActionCmdName ? \"public\" : \"private\");\n\n            new ActionGenerate()\n                    .setActionDocument(actionDocument)\n                    .setTemplate(template)\n                    .setFilePath(this.path)\n                    .setFileSuffix(\".cs\")\n                    .setTemplateCreator(this::ofTemplate)\n                    .generate();\n        });\n    }\n\n    @Override\n    protected void generateBroadcast(IoGameDocument ioGameDocument) {\n        Template template = ofTemplate(DocumentGenerateKit.broadcastActionTemplatePath);\n        template.binding(\"namespace\", this.namespace);\n\n        new BroadcastGenerate()\n                .setIoGameDocument(ioGameDocument)\n                .setTypeMappingDocument(typeMappingDocument)\n                .setTemplateCreator(this::ofTemplate)\n                .setTemplate(template)\n                .setFilePath(this.path)\n                .setFileSuffix(\".cs\")\n                .generate();\n    }\n\n    private Template ofTemplate(String fileName) {\n        return DocumentGenerateKit.getTemplate(\"csharp/\" + fileName);\n    }\n\n    private static class CSharpTypeMappingDocument implements TypeMappingDocument {\n        @Getter\n        final Map<Class<?>, TypeMappingRecord> map = new HashMap<>();\n        final CsharpDocumentGenerate documentGenerate;\n\n        public CSharpTypeMappingDocument(CsharpDocumentGenerate documentGenerate) {\n            this.documentGenerate = documentGenerate;\n            extractedInitTypeMapping();\n        }\n\n        private void extractedInitTypeMapping() {\n            // about int\n            var intTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"int\").setListParamTypeName(\"List<int>\")\n                    .setOfMethodTypeName(\"Int\").setOfMethodListTypeName(\"IntList\")\n                    .setResultMethodTypeName(\"GetInt()\").setResultMethodListTypeName(\"ListInt()\");\n\n            this.mapping(intTypeMapping, intClassList);\n\n            // about long\n            var longTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"long\").setListParamTypeName(\"List<long>\")\n                    .setOfMethodTypeName(\"Long\").setOfMethodListTypeName(\"LongList\")\n                    .setResultMethodTypeName(\"GetLong()\").setResultMethodListTypeName(\"ListLong()\");\n\n            this.mapping(longTypeMapping, longClassList);\n\n            // about boolean\n            var boolTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"bool\").setListParamTypeName(\"List<bool>\")\n                    .setOfMethodTypeName(\"Bool\").setOfMethodListTypeName(\"BoolList\")\n                    .setResultMethodTypeName(\"GetBool()\").setResultMethodListTypeName(\"ListBool()\");\n\n            this.mapping(boolTypeMapping, boolClassList);\n\n            // about String\n            var stringTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"string\").setListParamTypeName(\"List<string>\")\n                    .setOfMethodTypeName(\"String\").setOfMethodListTypeName(\"StringList\")\n                    .setResultMethodTypeName(\"GetString()\").setResultMethodListTypeName(\"ListString()\");\n\n            this.mapping(stringTypeMapping, stringClassList);\n        }\n\n        @Override\n        public TypeMappingRecord getTypeMappingRecord(Class<?> protoTypeClazz) {\n            var map = getMap();\n            if (map.containsKey(protoTypeClazz)) {\n                return map.get(protoTypeClazz);\n            }\n\n            var analyseImport = this.documentGenerate.analyseImport;\n            var protoMessage = analyseImport.getProtoMessage(protoTypeClazz);\n\n            String paramTypeName;\n            if (Objects.nonNull(protoMessage)) {\n                paramTypeName = protoMessage.getFullParamTypeName();\n            } else {\n                paramTypeName = protoTypeClazz.getSimpleName();\n            }\n\n            var record = new TypeMappingRecord().setInternalType(false)\n                    .setParamTypeName(paramTypeName).setListParamTypeName(\"List<%s>\".formatted(paramTypeName))\n                    .setOfMethodTypeName(\"\").setOfMethodListTypeName(\"ValueList\")\n                    .setResultMethodTypeName(\"GetValue<%s>()\".formatted(paramTypeName)).setResultMethodListTypeName(\"ListValue<%s>()\".formatted(paramTypeName));\n\n            map.put(protoTypeClazz, record);\n\n            return record;\n        }\n    }\n\n    private static class CsharpAnalyseImport {\n        final Map<Class<?>, CsProtoMessage> map = new HashMap<>();\n\n        CsharpAnalyseImport(Collection<ProtoFileMergeClass> messageList) {\n            messageList.forEach(protoFileMergeClass -> {\n                String filePackage = Arrays.stream(protoFileMergeClass.filePackage().split(\"\\\\.\"))\n                        .map(StrKit::firstCharToUpperCase)\n                        .collect(Collectors.joining(\".\"));\n\n                var message = new CsProtoMessage(filePackage, protoFileMergeClass.dataClass());\n                map.put(message.dataClass, message);\n            });\n        }\n\n        CsProtoMessage getProtoMessage(Class<?> dataClass) {\n            return map.get(dataClass);\n        }\n    }\n\n    private record CsProtoMessage(String filePackage, Class<?> dataClass) {\n        String getFullParamTypeName() {\n            return filePackage + \".\" + dataClass.getSimpleName();\n        }\n    }\n}"
  },
  {
    "path": "widget/generate-code/src/main/java/com/iohao/game/action/skeleton/core/doc/DocumentGenerateAbout.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.action.skeleton.core.ActionCommand;\nimport com.iohao.game.action.skeleton.protocol.wrapper.BoolValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.LongValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.StringValue;\nimport com.iohao.game.common.kit.ArrayKit;\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.common.kit.io.FileKit;\nimport com.iohao.game.common.kit.time.CacheTimeKit;\nimport com.iohao.game.common.kit.time.FormatTimeKit;\nimport com.iohao.game.widget.light.protobuf.ProtoFileMerge;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.SneakyThrows;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.experimental.UtilityClass;\nimport org.beetl.core.Configuration;\nimport org.beetl.core.Context;\nimport org.beetl.core.GroupTemplate;\nimport org.beetl.core.Template;\nimport org.beetl.core.resource.ClasspathResourceLoader;\nimport com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\n@UtilityClass\nclass DocumentGenerateKit {\n    private GroupTemplate gt;\n    String broadcastActionTemplatePath = \"broadcast_action.txt\";\n    String broadcastExampleTemplatePath = \"broadcast_action_example.txt\";\n    String broadcastExampleActionTemplatePath = \"broadcast_action_example_action.txt\";\n    String actionMethodResultExampleTemplatePath = \"action_method_result_example.txt\";\n    String gameCodeTemplatePath = \"game_code.txt\";\n    String actionTemplatePath = \"action.txt\";\n\n    static {\n        init();\n    }\n\n    @SneakyThrows\n    private void init() {\n        ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(\"generate/\");\n        Configuration cfg = Configuration.defaultConfiguration();\n        gt = new GroupTemplate(resourceLoader, cfg);\n        gt.registerFunction(\"codeEscape\", new ExampleCodeEscape());\n        gt.registerFunction(\"originalCode\", new ExampleOriginalCode());\n        gt.registerFunction(\"snakeName\", new SnakeName());\n    }\n\n    Template getTemplate(String path) {\n        return gt.getTemplate(path);\n    }\n\n    String toSnakeName(String name) {\n        return Optional.ofNullable(name)\n                .filter(StrKit::isNotEmpty)\n                .map(input -> {\n                    StringBuilder result = new StringBuilder();\n                    result.append(Character.toLowerCase(input.charAt(0)));\n\n                    for (int i = 1; i < input.length(); i++) {\n                        char currentChar = input.charAt(i);\n                        if (Character.isUpperCase(currentChar)) {\n                            result.append('_');\n                            result.append(Character.toLowerCase(currentChar));\n                        } else {\n                            result.append(currentChar);\n                        }\n                    }\n\n                    return result.toString();\n                }).orElse(\"\");\n    }\n\n    class ExampleCodeEscape implements org.beetl.core.Function {\n        @Override\n        public Object call(Object[] paras, Context ctx) {\n            return Optional.ofNullable(paras[0])\n                    .map(Object::toString)\n                    .map(str -> {\n                        // Escape\n                        return str.replace(\"<\", \"&lt;\").replace(\">\", \"&gt;\");\n                    }).orElse(\"\");\n        }\n    }\n\n    class ExampleOriginalCode implements org.beetl.core.Function {\n        @Override\n        public Object call(Object[] paras, Context ctx) {\n            return Optional.ofNullable(paras[0])\n                    .map(Object::toString)\n                    .map(str -> {\n                        // Escape\n                        return str.replace(\"&lt;\", \"<\").replace(\"&gt;\", \">\");\n                    }).orElse(\"\");\n        }\n    }\n\n    class SnakeName implements org.beetl.core.Function {\n        @Override\n        public Object call(Object[] paras, Context ctx) {\n            var value = paras[0];\n            if (Objects.isNull(value)) {\n                return \"\";\n            }\n\n            return toSnakeName(value.toString());\n        }\n    }\n}\n\n\n@Accessors(chain = true)\n@Setter(AccessLevel.PACKAGE)\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class GameCodeGenerate {\n    IoGameDocument ioGameDocument;\n    /** true : 生成框架内置的错误码，see {@link ActionErrorEnum} */\n    boolean internalErrorCode;\n\n    Template template;\n    String filePath;\n    String fileSuffix;\n\n    void generate() {\n        Objects.requireNonNull(ioGameDocument);\n        Objects.requireNonNull(template);\n        Objects.requireNonNull(filePath);\n        Objects.requireNonNull(fileSuffix);\n\n        List<ErrorCodeDocument> errorCodeDocumentList = ioGameDocument\n                .getErrorCodeDocumentList()\n                .stream()\n                .filter(errorCodeDocument -> internalErrorCode || errorCodeDocument.getValue() >= 0)\n                .peek(errorCodeDocument -> {\n                    String name = StrKit.firstCharToUpperCase(errorCodeDocument.getName());\n                    errorCodeDocument.setName(name);\n                }).toList();\n\n        template.binding(\"errorCodeDocumentList\", errorCodeDocumentList);\n        GenerateInternalKit.binding(template);\n\n        String fileText = template.render();\n        String path = \"%s%sGameCode%s\".formatted(this.filePath, File.separator, this.fileSuffix);\n        FileKit.writeUtf8String(fileText, path);\n    }\n}\n\n@Accessors(chain = true)\n@Setter(AccessLevel.PACKAGE)\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class BroadcastGenerate {\n    IoGameDocument ioGameDocument;\n    TypeMappingDocument typeMappingDocument;\n\n    Template template;\n    String filePath;\n    String fileSuffix;\n    Function<String, Template> templateCreator;\n    Consumer<BroadcastDocument> broadcastRenderBeforeConsumer;\n\n    void generate() {\n        Objects.requireNonNull(ioGameDocument);\n        Objects.requireNonNull(typeMappingDocument);\n        Objects.requireNonNull(template);\n        Objects.requireNonNull(filePath);\n        Objects.requireNonNull(fileSuffix);\n        Objects.requireNonNull(templateCreator);\n\n        Collection<BroadcastDocument> broadcastDocumentList = this.listBroadcastDocument();\n        template.binding(\"broadcastDocumentList\", broadcastDocumentList);\n        GenerateInternalKit.binding(template);\n\n        String fileText = template.render();\n        String path = \"%s%sListener%s\".formatted(this.filePath, File.separator, this.fileSuffix);\n        FileKit.writeUtf8String(fileText, path);\n    }\n\n    Collection<BroadcastDocument> listBroadcastDocument() {\n        return ioGameDocument.getBroadcastDocumentList().stream()\n                .peek(broadcastDocument -> {\n                    // 如果没有指定方法名，则方法名使用下述规则\n                    String methodName = StrKit.firstCharToUpperCase(broadcastDocument.getMethodName());\n                    broadcastDocument.setMethodName(methodName);\n\n                    // 生成广播使用代码示例\n                    extractedBroadcastExampleCode(broadcastDocument);\n                }).toList();\n    }\n\n    private void extractedBroadcastExampleCode(BroadcastDocument broadcastDocument) {\n        Class<?> dataClass = broadcastDocument.getDataClass();\n        if (Objects.isNull(dataClass)) {\n            if (Objects.nonNull(broadcastRenderBeforeConsumer)) {\n                broadcastRenderBeforeConsumer.accept(broadcastDocument);\n            }\n            return;\n        }\n\n        TypeMappingRecord typeMappingRecord = typeMappingDocument.getTypeMappingRecord(dataClass);\n        broadcastDocument.setBizDataType(typeMappingRecord.getParamTypeName());\n        broadcastDocument.setDataTypeIsInternal(typeMappingRecord.isInternalType());\n        broadcastDocument.setResultMethodTypeName(typeMappingRecord.getResultMethodTypeName());\n        broadcastDocument.setResultMethodListTypeName(typeMappingRecord.getResultMethodListTypeName());\n        broadcastDocument.setDataActualTypeName(typeMappingRecord.getParamTypeName());\n\n        if (Objects.nonNull(broadcastRenderBeforeConsumer)) {\n            broadcastRenderBeforeConsumer.accept(broadcastDocument);\n        }\n\n        String exampleCode = render(broadcastDocument, typeMappingRecord, DocumentGenerateKit.broadcastExampleTemplatePath);\n        broadcastDocument.setExampleCode(exampleCode);\n\n        // code action\n        String examplePrefixCode = render(broadcastDocument, typeMappingRecord, DocumentGenerateKit.broadcastExampleActionTemplatePath);\n        broadcastDocument.setExampleCodeAction(examplePrefixCode);\n    }\n\n    private String render(BroadcastDocument broadcastDocument, TypeMappingRecord typeMappingRecord, String examplePath) {\n        Template exampleTemplate = templateCreator.apply(examplePath);\n\n        if (Objects.isNull(exampleTemplate)) {\n            return \"\";\n        }\n\n        exampleTemplate.binding(\"_root\", broadcastDocument);\n        exampleTemplate.binding(\"typeMappingRecord\", typeMappingRecord);\n\n        return exampleTemplate.render().trim();\n    }\n}\n\n@Accessors(chain = true)\n@Setter(AccessLevel.PACKAGE)\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class ActionGenerate {\n    ActionDocument actionDocument;\n    /** action template */\n    Template template;\n    String filePath;\n    String fileSuffix;\n\n    Function<String, Template> templateCreator;\n\n    void generate() {\n        ActionDoc actionDoc = actionDocument.getActionDoc();\n        String classComment = actionDoc.getJavaClassDocInfo().getComment();\n        template.binding(\"classComment\", classComment);\n        GenerateInternalKit.binding(template);\n\n        // 路由成员变量\n        List<ActionMemberCmdDocument> actionMemberCmdDocumentList = actionDocument.getActionMemberCmdDocumentList();\n        template.binding(\"actionMemberCmdDocumentList\", actionMemberCmdDocumentList);\n\n        // action method\n        List<String> renderMethodList = actionDocument\n                .getActionMethodDocumentList()\n                .stream()\n                .map(actionMethodDocument -> {\n                    // example template code\n                    var exampleTemplate = this.templateCreator.apply(DocumentGenerateKit.actionMethodResultExampleTemplatePath);\n                    exampleTemplate.binding(\"_root\", actionMethodDocument);\n                    String exampleCode = exampleTemplate.render().trim();\n\n                    // generate method\n                    String templateFileName = getActionMethodDocumentTemplateFileName(actionMethodDocument);\n                    Template methodTemplate = this.templateCreator.apply(templateFileName);\n                    methodTemplate.binding(\"_root\", actionMethodDocument);\n                    methodTemplate.binding(\"exampleCode\", exampleCode);\n\n                    if (actionMethodDocument.internalBizDataType) {\n                        methodTemplate.binding(\"protoPrefix\", \"\");\n                    }\n\n                    return methodTemplate.render();\n                })\n                .toList();\n\n        template.binding(\"methodCodeList\", renderMethodList);\n\n        Class<?> controllerClazz = actionDoc.getControllerClazz();\n        String simpleName = controllerClazz.getSimpleName();\n        template.binding(\"ActionName\", simpleName);\n\n        String actionComment = actionDoc.getJavaClassDocInfo().getComment();\n        template.binding(\"ActionComment\", actionComment);\n\n        String render = template.render();\n        String actionFilePath = \"%s%s%s%s\".formatted(this.filePath, File.separator, simpleName, this.fileSuffix);\n        FileKit.writeUtf8String(render, actionFilePath);\n    }\n\n    private String getActionMethodDocumentTemplateFileName(ActionMethodDocument actionMethodDocument) {\n        if (actionMethodDocument.isVoid()) {\n            return actionMethodDocument.isHasBizData()\n                    ? \"action_method_void.txt\"\n                    : \"action_method_void_no_param.txt\";\n\n        } else {\n            return actionMethodDocument.isHasBizData()\n                    ? \"action_method.txt\"\n                    : \"action_method_no_param.txt\";\n        }\n    }\n}\n\n@UtilityClass\nclass GenerateInternalKit {\n    void binding(Template template) {\n        var generateTime = FormatTimeKit.ofPattern(\"yyyy-MM-dd\").format(CacheTimeKit.nowLocalDate());\n        String gtKey = new String(new byte[]{103, 101, 110, 101, 114, 97, 116, 101, 84, 105, 109, 101}, StandardCharsets.UTF_8);\n        template.binding(gtKey, \"// %s %s\".formatted(gtKey, generateTime));\n\n        String iohao = new String(new byte[]{105, 111, 71, 97, 109, 101, 72, 111, 109, 101}, StandardCharsets.UTF_8);\n        String u = new String(new byte[]{104, 116, 116, 112, 115, 58, 47, 47, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 105, 111, 104, 97, 111, 47, 105, 111, 71, 97, 109, 101}, StandardCharsets.UTF_8);\n        template.binding(iohao, \"// %s\".formatted(u));\n    }\n}\n\n@UtilityClass\nclass InternalProtoClassKit {\n    final Map<Class<?>, ProtoFileMergeClass> protoClassMap = new NonBlockingHashMap<>();\n\n    void analyseProtoClass(IoGameDocument ioGameDocument) {\n        if (!protoClassMap.isEmpty()) {\n            return;\n        }\n\n        // --------- collect proto class ---------\n        final Set<Class<?>> protoClassSet = new HashSet<>();\n        ioGameDocument.actionDocList.stream()\n                .flatMap(actionDoc -> actionDoc.getActionCommandDocMap().values().stream())\n                .forEach(actionCommandDoc -> {\n                    ActionCommand actionCommand = actionCommandDoc.getActionCommand();\n                    // --------- action return class ---------\n                    ActionCommand.ActionMethodReturnInfo returnInfo = actionCommand.getActionMethodReturnInfo();\n                    if (!returnInfo.isVoid()) {\n                        Class<?> returnTypeClazz = returnInfo.getActualTypeArgumentClazz();\n                        protoClassSet.add(returnTypeClazz);\n                    }\n\n                    // --------- action param class ---------\n                    ActionCommand.ParamInfo bizParam = DocumentAnalyseKit.getBizParam(actionCommand);\n                    if (Objects.nonNull(bizParam)) {\n                        Class<?> actualTypeArgumentClazz = bizParam.getActualTypeArgumentClazz();\n                        protoClassSet.add(actualTypeArgumentClazz);\n                    }\n                });\n\n        ioGameDocument.broadcastDocumentList.forEach(broadcastDocument -> {\n            Class<?> dataClass = broadcastDocument.getDataClass();\n            if (Objects.nonNull(dataClass)) {\n                protoClassSet.add(dataClass);\n            }\n        });\n\n        var excludeTypeList = List.of(\n                int.class, Integer.class, IntValue.class,\n                long.class, Long.class, LongValue.class,\n                boolean.class, Boolean.class, BoolValue.class,\n                String.class, StringValue.class\n        );\n\n        protoClassSet.stream().filter(protoClass -> {\n            for (Class<?> aClass : excludeTypeList) {\n                if (aClass == protoClass) {\n                    return false;\n                }\n            }\n\n            return true;\n        }).forEach(protoClass -> {\n            ProtoFileMerge annotation = protoClass.getAnnotation(ProtoFileMerge.class);\n            if (Objects.isNull(annotation)) {\n                return;\n            }\n\n            String fileName = annotation.fileName();\n            String filePackage = annotation.filePackage();\n\n            var message = new ProtoFileMergeClass(fileName, filePackage, protoClass);\n            protoClassMap.put(protoClass, message);\n        });\n    }\n}\n\nrecord ProtoFileMergeClass(String fileName, String filePackage, Class<?> dataClass) {\n}\n\n@Setter\n@FieldDefaults(level = AccessLevel.PACKAGE)\nabstract class AbstractDocumentGenerate implements DocumentGenerate {\n    @Getter\n    @Deprecated\n    final Set<String> actionImportList = new LinkedHashSet<>();\n    @Getter\n    @Deprecated\n    final Set<String> broadcastImportList = new LinkedHashSet<>();\n    @Getter\n    @Deprecated\n    final Set<String> errorCodeImportList = new LinkedHashSet<>();\n    /** true : generate ActionErrorEnum */\n    boolean internalErrorCode = true;\n    /** your .proto path */\n    @Deprecated\n    String protoImportPath;\n    boolean publicActionCmdName;\n    /**\n     * The storage path of the generated files.\n     * By default, it will be generated in the ./target/action directory\n     */\n    String path = ArrayKit.join(new String[]{System.getProperty(\"user.dir\"), \"target\", \"code\"}, File.separator);\n    TypeMappingDocument typeMappingDocument;\n\n    protected abstract void generateAction(IoGameDocument ioGameDocument);\n\n    protected abstract void generateBroadcast(IoGameDocument ioGameDocument);\n\n    protected abstract void generateErrorCode(IoGameDocument ioGameDocument);\n}"
  },
  {
    "path": "widget/generate-code/src/main/java/com/iohao/game/action/skeleton/core/doc/GDScriptDocumentGenerate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.beetl.core.Template;\n\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\n\n/**\n * @author 渔民小镇\n * @date 2025-05-09\n * @since 21.27\n */\n@Slf4j\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class GDScriptDocumentGenerate extends AbstractDocumentGenerate {\n\n    @Setter(AccessLevel.PRIVATE)\n    GDScriptAnalyseImport analyseImport;\n\n    public GDScriptDocumentGenerate() {\n        this.typeMappingDocument = new GDScriptTypeMappingDocument(this);\n    }\n\n    @Override\n    public void generate(IoGameDocument ioGameDocument) {\n        InternalProtoClassKit.analyseProtoClass(ioGameDocument);\n        Map<Class<?>, ProtoFileMergeClass> protoClassMap = InternalProtoClassKit.protoClassMap;\n        this.analyseImport = new GDScriptAnalyseImport(protoClassMap.values());\n\n        this.generateAction(ioGameDocument);\n        this.generateBroadcast(ioGameDocument);\n        this.generateErrorCode(ioGameDocument);\n\n        log.info(\"GDScriptDocumentGenerate success: {}\", this.path);\n    }\n\n\n    @Override\n    protected void generateAction(IoGameDocument ioGameDocument) {\n        List<ActionDocument> actionDocumentList = DocumentAnalyseKit.analyseActionDocument(ioGameDocument, typeMappingDocument);\n        extractedSnakeName(actionDocumentList);\n        List<Class<?>> protoMessageClassList = new ArrayList<>();\n\n        actionDocumentList.forEach(actionDocument -> {\n\n            // collect the action inputParam and outputParam\n            List<ActionMethodDocument> actionMethodDocumentList = actionDocument.getActionMethodDocumentList();\n            actionMethodDocumentList.forEach(actionMethodDocument -> {\n                Class<?> bizDataTypeClazz = actionMethodDocument.bizDataTypeClazz;\n                Class<?> returnTypeClazz = actionMethodDocument.returnTypeClazz;\n\n                GDScriptDocumentGenerate.GDScriptProtoMessage bizDataProtoMessage = this.analyseImport.getProtoMessage(bizDataTypeClazz);\n                GDScriptDocumentGenerate.GDScriptProtoMessage returnProtoMessage = this.analyseImport.getProtoMessage(returnTypeClazz);\n\n                if (Objects.nonNull(bizDataProtoMessage)) {\n                    protoMessageClassList.add(bizDataProtoMessage.dataClass);\n                }\n\n                if (Objects.nonNull(returnProtoMessage)) {\n                    protoMessageClassList.add(returnProtoMessage.dataClass);\n                }\n            });\n\n            String imports = this.analyseImport.getImportPath(protoMessageClassList);\n\n            Template template = ofTemplate(\"action.txt\");\n            template.binding(\"imports\", imports);\n\n            new ActionGenerate()\n                    .setActionDocument(actionDocument)\n                    .setTemplate(template)\n                    .setFilePath(this.path)\n                    .setFileSuffix(\".gd\")\n                    .setTemplateCreator(this::ofTemplate)\n                    .generate();\n        });\n    }\n\n    private void extractedSnakeName(List<ActionDocument> actionDocumentList) {\n        for (ActionDocument actionDocument : actionDocumentList) {\n            for (ActionMemberCmdDocument memberCmdDocument : actionDocument.getActionMemberCmdDocumentList()) {\n                memberCmdDocument.memberName = toSnakeName(memberCmdDocument.memberName);\n            }\n\n            for (ActionMethodDocument methodDocument : actionDocument.getActionMethodDocumentList()) {\n                methodDocument.actionMethodName = toSnakeName(methodDocument.actionMethodName);\n                methodDocument.bizDataName = toSnakeName(methodDocument.bizDataName);\n                methodDocument.memberCmdName = toSnakeName(methodDocument.memberCmdName);\n            }\n        }\n    }\n\n    private String toSnakeName(String s) {\n        return DocumentGenerateKit.toSnakeName(s);\n    }\n\n    private Template ofTemplate(String fileName) {\n        var template = DocumentGenerateKit.getTemplate(\"gdscript/\" + fileName);\n        template.binding(\"protoPrefix\", \"IoGame\");\n        return template;\n    }\n\n    @Override\n    protected void generateBroadcast(IoGameDocument ioGameDocument) {\n\n        Consumer<BroadcastDocument> broadcastRenderBeforeConsumer = broadcastDocument -> {\n            broadcastDocument.cmdMethodName = toSnakeName(broadcastDocument.cmdMethodName);\n            broadcastDocument.methodName = toSnakeName(broadcastDocument.methodName);\n        };\n\n        // collect protoMessage\n        List<Class<?>> protoMessageClassList = new ArrayList<>();\n        ioGameDocument.getBroadcastDocumentList().forEach(broadcastDocument -> {\n            Class<?> dataClass = broadcastDocument.getDataClass();\n            GDScriptDocumentGenerate.GDScriptProtoMessage bizDataProtoMessage = this.analyseImport.getProtoMessage(dataClass);\n            if (Objects.nonNull(bizDataProtoMessage)) {\n                protoMessageClassList.add(dataClass);\n            }\n        });\n\n        String imports = this.analyseImport.getImportPath(protoMessageClassList);\n\n        Template template = ofTemplate(DocumentGenerateKit.broadcastActionTemplatePath);\n        template.binding(\"imports\", imports);\n\n        new BroadcastGenerate()\n                .setIoGameDocument(ioGameDocument)\n                .setTypeMappingDocument(typeMappingDocument)\n                .setTemplateCreator(this::ofTemplate)\n                .setTemplate(template)\n                .setFilePath(this.path)\n                .setFileSuffix(\".gd\")\n                .setBroadcastRenderBeforeConsumer(broadcastRenderBeforeConsumer)\n                .generate();\n    }\n\n    @Override\n    protected void generateErrorCode(IoGameDocument ioGameDocument) {\n        Template template = ofTemplate(\"game_code.txt\");\n\n        new GameCodeGenerate()\n                .setIoGameDocument(ioGameDocument)\n                .setInternalErrorCode(this.internalErrorCode)\n                .setTemplate(template)\n                .setFilePath(this.path)\n                .setFileSuffix(\".gd\")\n                .generate();\n    }\n\n    private static class GDScriptTypeMappingDocument implements TypeMappingDocument {\n        @Getter\n        final Map<Class<?>, TypeMappingRecord> map = new HashMap<>();\n        final GDScriptDocumentGenerate documentGenerate;\n\n        public GDScriptTypeMappingDocument(GDScriptDocumentGenerate documentGenerate) {\n            this.documentGenerate = documentGenerate;\n            this.extractedInitTypeMapping();\n        }\n\n        private void extractedInitTypeMapping() {\n            // about int\n            var intTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"int\").setListParamTypeName(\"Array[int]\")\n                    .setOfMethodTypeName(\"int\").setOfMethodListTypeName(\"int_list\")\n                    .setResultMethodTypeName(\"get_int()\").setResultMethodListTypeName(\"list_int()\");\n\n            this.mapping(intTypeMapping, intClassList);\n\n            // about long\n            var longTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"int\").setListParamTypeName(\"Array[int]\")\n                    .setOfMethodTypeName(\"long\").setOfMethodListTypeName(\"long_list\")\n                    .setResultMethodTypeName(\"get_long()\").setResultMethodListTypeName(\"list_long()\");\n\n            this.mapping(longTypeMapping, longClassList);\n\n            // about boolean\n            var boolTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"bool\").setListParamTypeName(\"Array[bool]\")\n                    .setOfMethodTypeName(\"bool\").setOfMethodListTypeName(\"bool_list\")\n                    .setResultMethodTypeName(\"get_bool()\").setResultMethodListTypeName(\"list_bool()\");\n\n            this.mapping(boolTypeMapping, boolClassList);\n\n            // about String\n            var stringTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"String\").setListParamTypeName(\"Array[String]\")\n                    .setOfMethodTypeName(\"string\").setOfMethodListTypeName(\"string_list\")\n                    .setResultMethodTypeName(\"get_string()\").setResultMethodListTypeName(\"list_string()\");\n\n            this.mapping(stringTypeMapping, stringClassList);\n        }\n\n        @Override\n        public TypeMappingRecord getTypeMappingRecord(Class<?> protoTypeClazz) {\n            var map = getMap();\n            if (map.containsKey(protoTypeClazz)) {\n                return map.get(protoTypeClazz);\n            }\n\n            var analyseImport = this.documentGenerate.analyseImport;\n            var protoMessage = analyseImport.getProtoMessage(protoTypeClazz);\n\n            String paramTypeName;\n            if (Objects.isNull(protoMessage)) {\n                paramTypeName = protoTypeClazz.getSimpleName();\n            } else {\n                paramTypeName = protoMessage.getFullParamTypeName();\n            }\n\n            var record = new TypeMappingRecord().setInternalType(false)\n                    .setParamTypeName(paramTypeName).setListParamTypeName(\"Array[%s]\".formatted(paramTypeName))\n                    .setOfMethodTypeName(\"\").setOfMethodListTypeName(\"value_list\")\n                    .setResultMethodTypeName(\"get_value(%s)\".formatted(paramTypeName)).setResultMethodListTypeName(\"list_value(%s)\".formatted(paramTypeName));\n\n            map.put(protoTypeClazz, record);\n\n            return record;\n        }\n    }\n\n    private static class GDScriptAnalyseImport {\n        final Map<Class<?>, GDScriptProtoMessage> map = new HashMap<>();\n\n        GDScriptAnalyseImport(Collection<ProtoFileMergeClass> messageList) {\n            messageList.forEach(protoFileMergeClass -> {\n                String fileName = protoFileMergeClass.fileName();\n                String importFileName = fileName.substring(0, fileName.length() - \".proto\".length());\n\n                var message = new GDScriptProtoMessage(importFileName, protoFileMergeClass.dataClass());\n                map.put(message.dataClass, message);\n            });\n        }\n\n        GDScriptProtoMessage getProtoMessage(Class<?> dataClass) {\n            return map.get(dataClass);\n        }\n\n        String getImportPath(List<? extends Class<?>> dataClassList) {\n            Set<String> importFileNameSet = new HashSet<>();\n            for (Class<?> dataClass : dataClassList) {\n                GDScriptDocumentGenerate.GDScriptProtoMessage protoMessage = getProtoMessage(dataClass);\n                String importFileName = protoMessage.importFileName;\n                importFileNameSet.add(importFileName);\n            }\n\n            // preload .proto (proto.gd)\n            return importFileNameSet.stream().map(importFileName -> {\n                String tpl = \"const %s = preload(\\\"../%s.gd\\\")\";\n                String variateName = StrKit.firstCharToUpperCase(importFileName);\n                return tpl.formatted(variateName, importFileName);\n            }).collect(Collectors.joining(\"\\n\"));\n        }\n    }\n\n    private record GDScriptProtoMessage(String importFileName, Class<?> dataClass) {\n        String getFullParamTypeName() {\n            return StrKit.firstCharToUpperCase(importFileName) + \".\" + dataClass.getSimpleName();\n        }\n    }\n}\n"
  },
  {
    "path": "widget/generate-code/src/main/java/com/iohao/game/action/skeleton/core/doc/TypeScriptDocumentGenerate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.action.skeleton.core.doc;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.beetl.core.Template;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * Generate TypeScript code, such as broadcast, error code, action\n *\n * @author 渔民小镇\n * @date 2024-12-01\n * @since 21.21\n */\n@Slf4j\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class TypeScriptDocumentGenerate extends AbstractDocumentGenerate {\n    //    String protoPrefix = \"ioGame.\";\n    String protoPrefix = \"\";\n    @Setter(AccessLevel.PRIVATE)\n    TypeScriptAnalyseImport analyseImport;\n\n    public TypeScriptDocumentGenerate() {\n        this.typeMappingDocument = new TypeScriptMappingDocument(this);\n    }\n\n    @Override\n    public void generate(IoGameDocument ioGameDocument) {\n        Objects.requireNonNull(this.path);\n\n        InternalProtoClassKit.analyseProtoClass(ioGameDocument);\n        Map<Class<?>, ProtoFileMergeClass> protoClassMap = InternalProtoClassKit.protoClassMap;\n        this.analyseImport = new TypeScriptAnalyseImport(protoClassMap.values());\n\n        this.generateAction(ioGameDocument);\n        this.generateBroadcast(ioGameDocument);\n        this.generateErrorCode(ioGameDocument);\n\n        log.info(\"TypeScriptDocumentGenerate success: {}\", this.path);\n    }\n\n    @Override\n    protected void generateErrorCode(IoGameDocument ioGameDocument) {\n        Template template = ofTemplate(DocumentGenerateKit.gameCodeTemplatePath);\n\n        new GameCodeGenerate()\n                .setIoGameDocument(ioGameDocument)\n                .setInternalErrorCode(this.internalErrorCode)\n                .setTemplate(template)\n                .setFilePath(this.path)\n                .setFileSuffix(\".ts\")\n                .generate();\n    }\n\n    @Override\n    protected void generateBroadcast(IoGameDocument ioGameDocument) {\n\n        List<Class<?>> protoMessageClassList = new ArrayList<>();\n        ioGameDocument.getBroadcastDocumentList().forEach(broadcastDocument -> {\n            Class<?> dataClass = broadcastDocument.getDataClass();\n            TsProtoMessage bizDataProtoMessage = this.analyseImport.getProtoMessage(dataClass);\n            if (Objects.nonNull(bizDataProtoMessage)) {\n                protoMessageClassList.add(dataClass);\n            }\n        });\n\n        String imports = this.analyseImport.getImportPath(protoMessageClassList);\n\n        Template template = ofTemplate(DocumentGenerateKit.broadcastActionTemplatePath);\n        template.binding(\"imports\", imports);\n\n        new BroadcastGenerate()\n                .setIoGameDocument(ioGameDocument)\n                .setTypeMappingDocument(typeMappingDocument)\n                .setTemplateCreator(this::ofTemplate)\n                .setTemplate(template)\n                .setFilePath(this.path)\n                .setFileSuffix(\".ts\")\n                .generate();\n    }\n\n    @Override\n    protected void generateAction(IoGameDocument ioGameDocument) {\n        List<ActionDocument> actionDocumentList = DocumentAnalyseKit.analyseActionDocument(ioGameDocument, typeMappingDocument);\n\n        List<Class<?>> protoMessageClassList = new ArrayList<>();\n        actionDocumentList.forEach(actionDocument -> {\n\n            // collect the action inputParam and outputParam\n            List<ActionMethodDocument> actionMethodDocumentList = actionDocument.getActionMethodDocumentList();\n            actionMethodDocumentList.forEach(actionMethodDocument -> {\n                Class<?> bizDataTypeClazz = actionMethodDocument.bizDataTypeClazz;\n                Class<?> returnTypeClazz = actionMethodDocument.returnTypeClazz;\n\n                TsProtoMessage bizDataProtoMessage = this.analyseImport.getProtoMessage(bizDataTypeClazz);\n                TsProtoMessage returnProtoMessage = this.analyseImport.getProtoMessage(returnTypeClazz);\n\n                if (Objects.nonNull(bizDataProtoMessage)) {\n                    protoMessageClassList.add(bizDataProtoMessage.dataClass);\n                }\n\n                if (Objects.nonNull(returnProtoMessage)) {\n                    protoMessageClassList.add(returnProtoMessage.dataClass);\n                }\n            });\n\n            String imports = this.analyseImport.getImportPath(protoMessageClassList);\n\n            Template template = ofTemplate(DocumentGenerateKit.actionTemplatePath);\n            // imports\n            template.binding(\"imports\", imports);\n            template.binding(\"publicActionCmdName\", this.publicActionCmdName ? \"public\" : \"private\");\n\n            new ActionGenerate()\n                    .setActionDocument(actionDocument)\n                    .setTemplate(template)\n                    .setFilePath(this.path)\n                    .setFileSuffix(\".ts\")\n                    .setTemplateCreator(this::ofTemplate)\n                    .generate();\n        });\n    }\n\n    private Template ofTemplate(String fileName) {\n        var template = DocumentGenerateKit.getTemplate(\"ts/\" + fileName);\n        template.binding(\"protoPrefix\", Objects.isNull(this.protoPrefix) ? \"\" : this.protoPrefix);\n        return template;\n    }\n\n    private static class TypeScriptMappingDocument implements TypeMappingDocument {\n        @Getter\n        final Map<Class<?>, TypeMappingRecord> map = new HashMap<>();\n        final TypeScriptDocumentGenerate documentGenerate;\n\n        public TypeScriptMappingDocument(TypeScriptDocumentGenerate documentGenerate) {\n            this.documentGenerate = documentGenerate;\n            extractedInitTypeMapping();\n        }\n\n        private void extractedInitTypeMapping() {\n            // about int\n            var intTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"number\").setListParamTypeName(\"number[]\")\n                    .setOfMethodTypeName(\"Int\").setOfMethodListTypeName(\"IntList\")\n                    .setResultMethodTypeName(\"getInt\").setResultMethodListTypeName(\"listInt\");\n\n            this.mapping(intTypeMapping, intClassList);\n\n            // about long\n            var longTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"bigint\").setListParamTypeName(\"bigint[]\")\n                    .setOfMethodTypeName(\"Long\").setOfMethodListTypeName(\"LongList\")\n                    .setResultMethodTypeName(\"getLong\").setResultMethodListTypeName(\"listLong\");\n\n            this.mapping(longTypeMapping, longClassList);\n\n            // about boolean\n            var boolTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"boolean\").setListParamTypeName(\"boolean[]\")\n                    .setOfMethodTypeName(\"Bool\").setOfMethodListTypeName(\"BoolList\")\n                    .setResultMethodTypeName(\"getBool\").setResultMethodListTypeName(\"listBool\");\n\n            this.mapping(boolTypeMapping, boolClassList);\n\n            // about String\n            var stringTypeMapping = new TypeMappingRecord()\n                    .setParamTypeName(\"string\").setListParamTypeName(\"string[]\")\n                    .setOfMethodTypeName(\"String\").setOfMethodListTypeName(\"StringList\")\n                    .setResultMethodTypeName(\"getString\").setResultMethodListTypeName(\"listString\");\n\n            this.mapping(stringTypeMapping, stringClassList);\n        }\n\n        @Override\n        public TypeMappingRecord getTypeMappingRecord(Class<?> protoTypeClazz) {\n            var map = getMap();\n            if (map.containsKey(protoTypeClazz)) {\n                return map.get(protoTypeClazz);\n            }\n\n            var analyseImport = documentGenerate.analyseImport;\n            var protoMessage = analyseImport.getProtoMessage(protoTypeClazz);\n\n            String paramTypeName;\n            if (Objects.nonNull(protoMessage)) {\n                paramTypeName = protoMessage.getFullParamTypeName();\n            } else {\n                paramTypeName = protoTypeClazz.getSimpleName();\n            }\n\n            return new TypeMappingRecord()\n                    .setInternalType(false)\n                    .setParamTypeName(paramTypeName).setListParamTypeName(\"%s[]\".formatted(paramTypeName))\n                    .setOfMethodTypeName(\"\").setOfMethodListTypeName(\"ValueList\")\n                    .setResultMethodTypeName(\"getValue\").setResultMethodListTypeName(\"listValue\");\n        }\n    }\n\n    private static class TypeScriptAnalyseImport {\n        final Map<Class<?>, TsProtoMessage> map = new HashMap<>();\n\n        TypeScriptAnalyseImport(Collection<ProtoFileMergeClass> messageList) {\n            messageList.forEach(protoFileMergeClass -> {\n                String fileName = protoFileMergeClass.fileName();\n                String simpleFileName = fileName.substring(0, fileName.lastIndexOf(\".proto\"));\n                String importFileName = \"%s_pb\".formatted(simpleFileName);\n\n                var message = new TsProtoMessage(importFileName, protoFileMergeClass.dataClass());\n                map.put(message.dataClass, message);\n            });\n        }\n\n        TsProtoMessage getProtoMessage(Class<?> dataClass) {\n            return map.get(dataClass);\n        }\n\n        String getImportPath(List<? extends Class<?>> dataClassList) {\n            Set<String> importFileNameSet = new HashSet<>();\n            for (Class<?> dataClass : dataClassList) {\n                TsProtoMessage protoMessage = getProtoMessage(dataClass);\n                String importFileName = protoMessage.importFileName;\n                importFileNameSet.add(importFileName);\n            }\n\n            // import .proto file\n            return importFileNameSet.stream().map(importFileName -> {\n                String tpl = \"import * as %s from \\\"../%s\\\";\";\n                return tpl.formatted(importFileName, importFileName);\n            }).collect(Collectors.joining(\"\\n\"));\n        }\n    }\n\n    private record TsProtoMessage(String importFileName, Class<?> dataClass) {\n        String getFullParamTypeName() {\n            return importFileName + \".\" + dataClass.getSimpleName();\n        }\n    }\n}\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/action.txt",
    "content": "${generateTime}\n${ioGameHome}\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Collections.Generic;\nusing Google.Protobuf;\nusing IoGame.Sdk;\n\nnamespace ${namespace}\n{\n  /// <summary>\n  /// ${ActionComment}\n  /// </summary>\n  /// <remarks>Author: https://github.com/iohao/ioGame</remarks>\n  public static class ${ActionName}\n  {\n  <%for(o in actionMemberCmdDocumentList) {%>\n    ${publicActionCmdName} static readonly int ${o.memberName} = CmdKit.MappingRequest(${o.cmdMerge}, \"${o.comment}\");\n  <%}%>\n\n  <%for(o in methodCodeList) {%>\n${o}\n  <%}%>\n  }\n}"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/action_method.txt",
    "content": "    /// <summary>\n    /// ${methodComment}\n    /// </summary>\n    /// <param name=\"${bizDataName}\">${bizDataComment}</param>\n    /// <param name=\"callback\">${returnDataIsList?\"list of \"}<see cref=\"${returnDataName}\"/> ${returnComment}</param>\n    /// <returns>RequestCommand</returns>\n    /// <code>\n    /// // ${bizDataComment}\n    /// ${codeEscape(bizDataType)} ${bizDataName} = ...;\n    /// ${actionSimpleName}.of${actionMethodName}(${bizDataName}, result => {\n    ///     // ioGame: your biz code.\n    ///     // ${returnComment}\n    ///     ${exampleCode!}\n    ///     result.Log(value);\n    /// });\n    /// </code>\n    public static RequestCommand Of${actionMethodName}(${bizDataType} ${bizDataName}, CallbackDelegate callback)\n    {\n    <%if (bizDataTypeIsList && !internalBizDataType) {%>\n        var dataList = ${bizDataName}.Cast<IMessage>().ToList();\n        return RequestCommand.Of${sdkMethodName}(${memberCmdName}, dataList).OnCallback(callback).Execute();\n    <%} else {%>\n        return RequestCommand.Of${sdkMethodName!}(${memberCmdName}, ${bizDataName}).OnCallback(callback).Execute();\n    <%}%>\n    }\n\n    /// <summary>\n    /// ${methodComment}\n    /// </summary>\n    /// <param name=\"${bizDataName}\">${bizDataComment}</param>\n    /// <returns>ResponseResult，${returnDataIsList?\"list of \"}<see cref=\"${returnDataName}\"/> ${returnComment}</returns>\n    /// <code>\n    /// // ${bizDataComment}\n    /// ${codeEscape(bizDataType)} ${bizDataName} = ...;\n    /// var result = await ${actionSimpleName}.ofAwait${actionMethodName}(${bizDataName});\n    /// // ioGame: your biz code.\n    /// // ${returnComment}\n    /// ${exampleCode!}\n    /// result.Log(value);\n    /// </code>\n    public static async Task<ResponseResult> OfAwait${actionMethodName}(${bizDataType} ${bizDataName})\n    {\n    <%if (bizDataTypeIsList && !internalBizDataType) {%>\n        var dataList = ${bizDataName}.Cast<IMessage>().ToList();\n        return await RequestCommand.OfAwait${sdkMethodName}(${memberCmdName}, dataList);\n    <%} else {%>\n        return await RequestCommand.OfAwait${sdkMethodName}(${memberCmdName}, ${bizDataName});\n    <%}%>\n    }\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/action_method_no_param.txt",
    "content": "    /// <summary>\n    /// ${methodComment}\n    /// </summary>\n    /// <param name=\"callback\">${returnDataIsList?\"list of \"}<see cref=\"${returnDataName}\"/> ${returnComment}</param>\n    /// <returns>RequestCommand</returns>\n    /// <code>\n    /// ${actionSimpleName}.of${actionMethodName}(result => {\n    ///     // ioGame: your biz code.\n    ///     // ${returnComment}\n    ///     ${exampleCode!}\n    ///     result.Log(value);\n    /// });\n    /// </code>\n    public static RequestCommand Of${actionMethodName}(CallbackDelegate callback)\n    {\n        return RequestCommand.Of${sdkMethodName}(${memberCmdName}).OnCallback(callback).Execute();\n    }\n\n    /// <summary>\n    /// ${methodComment}\n    /// </summary>\n    /// <returns>ResponseResult，${returnDataIsList?\"list of \"}<see cref=\"${returnDataName}\"/> ${returnComment}</returns>\n    /// <code>\n    /// var result = await ${actionSimpleName}.ofAwait${actionMethodName}();\n    /// // ioGame: your biz code.\n    /// // ${returnComment}\n    /// ${exampleCode!}\n    /// result.Log(value);\n    /// </code>\n    public static async Task<ResponseResult> OfAwait${actionMethodName}()\n    {\n        return await RequestCommand.OfAwait${sdkMethodName}(${memberCmdName});\n    }\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/action_method_result_example.txt",
    "content": "<%\nvar resultMethodName;\nif (returnDataTypeIsInternal) {\n    // see CSharpDocumentGenerate.extractedInitTypeMapping, TypeMappingRecord\n    if (returnDataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar value = result.${resultMethodName};\n<%\n} else {\n    // see CSharpTypeMappingDocument.getTypeMappingRecord, ActionMethodDocument.extractedReturnInfo\n    if (returnDataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar value = result.${codeEscape(resultMethodName)};\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/action_method_void.txt",
    "content": "    /// <summary>\n    /// ${methodComment}\n    /// </summary>\n    /// <param name=\"${bizDataName}\">${bizDataComment}</param>\n    /// <returns>RequestCommand void</returns>\n    /// <code>\n    /// // ${bizDataComment}\n    /// ${codeEscape(bizDataType)} ${bizDataName} = ...;\n    /// ${actionSimpleName}.of${actionMethodName}(${bizDataName});\n    /// </code>\n    public static RequestCommand Of${actionMethodName}(${bizDataType} ${bizDataName})\n    {\n    <%if (bizDataTypeIsList && !internalBizDataType) {%>\n        var dataList = ${bizDataName}.Cast<IMessage>().ToList();\n        return RequestCommand.Of${sdkMethodName}(${memberCmdName}, dataList).Execute();\n    <%} else {%>\n        return RequestCommand.Of${sdkMethodName!}(${memberCmdName}, ${bizDataName}).Execute();\n    <%}%>\n    }\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/action_method_void_no_param.txt",
    "content": "    /// <summary>\n    /// ${methodComment}\n    /// </summary>\n    /// <returns>RequestCommand void</returns>\n    /// <code>\n    /// ${actionSimpleName}.of${actionMethodName}();\n    /// </code>\n    public static RequestCommand Of${actionMethodName}()\n    {\n        return RequestCommand.Of(${memberCmdName}).Execute();\n    }\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/broadcast_action.txt",
    "content": "${generateTime}\n${ioGameHome}\nusing IoGame.Sdk;\n\nnamespace ${namespace}\n{\n  /// <summary>\n  /// Broadcast Listener；广播监听；\n  /// </summary>\n  /// <remarks>Author: https://github.com/iohao/ioGame</remarks>\n  public static class Listener\n  {\n  <%for(o in broadcastDocumentList) {%>\n    private static readonly int ${o.cmdMethodName}_${o.cmd}_${o.subCmd} = CmdKit.MappingBroadcast(${o.cmdMerge},\"${o.methodDescription!}\");\n  <%}%>\n  <%for(o in broadcastDocumentList) {%>\n\n    /// <summary>\n    /// ${o.methodDescription}\n    /// </summary>\n    /// <param name=\"callback\">${o.dataIsList?\"list of \"}<see cref=\"${o.bizDataType}\"/> ${o.dataDescription}</param>\n    /// <code>\n    /// Listener.listen${o.methodName}(result => {\n    ///     // ${o.dataDescription!\"broadcast notification\"}\n    ///     ${o.exampleCode!}\n    /// });\n    /// </code>\n    public static void Listen${o.methodName}(CallbackDelegate callback)\n    {\n      ListenCommand.Of(${o.cmdMethodName}_${o.cmd}_${o.subCmd}, callback);\n    }\n  <%}%>\n\n    public static void Listener_ioGame()\n    {\n      // all listener\n    <%for(o in broadcastDocumentList) {%>\n\n      Listen${o.methodName}(result =>\n      {\n        var mergeTitle = CmdKit.ToString(result.GetCmdMerge());\n        var title = CmdKit.GetBroadcastTitle(result.GetCmdMerge());\n\n    <% if (o.exampleCodeAction == null) { %>\n        IoGameSetting.GameGameConsole.Log($\"{mergeTitle}, {title}\");\n    <% } else { %>\n        ${originalCode(o.exampleCodeAction)}\n        IoGameSetting.GameGameConsole.Log($\"{mergeTitle}, {title}, {value}\");\n    <%}%>\n      });\n   <%}%>\n\n    }\n  }\n}"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/broadcast_action_example.txt",
    "content": "<%\nvar resultMethodName;\nif (dataTypeIsInternal) {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar value = result.${resultMethodName};\n<%\n} else {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar value = result.${codeEscape(resultMethodName)};\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/broadcast_action_example_action.txt",
    "content": "<%\nvar resultMethodName;\nif (dataTypeIsInternal) {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar value = result.${resultMethodName};\n<%\n} else {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar value = result.${codeEscape(resultMethodName)};\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/csharp/game_code.txt",
    "content": "${generateTime}\n${ioGameHome}\nusing System.Collections.Generic;\nusing IoGame.Sdk;\n\nnamespace ${namespace}\n{\n    /// <summary>\n    /// GameCode、ErrorCode. 游戏错误码\n    /// </summary>\n    /// <remarks>Author: https://github.com/iohao/ioGame</remarks>\n    public static class GameCode\n    {\n    <%for(o in errorCodeDocumentList) {%>\n        /// <summary>\n        /// ${o.description}\n        /// </summary>\n        public static readonly int ${o.name} = CmdKit.MappingErrorCode(${o.value}, \"${o.description!}\");\n\n    <%}%>\n        public static void Init() {\n            // trigger errorCodeMapping init.\n            // 调用一次方法，触发错误码的初始化操作。\n            var ints = new List<int>\n            {\n            <%for(o in errorCodeDocumentList) {%>\n                ${o.name},\n            <%}%>0\n            };\n        }\n    }\n}"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/action.txt",
    "content": "# ${generateTime}\n# ${ioGameHome}\n\n## ${ActionComment}[br]\n## https://github.com/iohao/ioGame\nclass_name ${ActionName}\nextends RefCounted\n\n${imports}\n\n<%for(o in actionMemberCmdDocumentList) {%>\nstatic var _${o.memberName}: int = IoGame.CmdKit.mapping_request(${o.cmdMerge}, \"${o.comment}\")\n<%}%>\n\n<%for(o in methodCodeList) {%>\n${o}\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/action_method.txt",
    "content": "## ${methodComment}[br]\n##\n## [br][b]@param ${bizDataName}:[/b] ${bizDataComment}\n## [br][b]@param callback:[/b] ${returnComment} (returnType: [code]${returnDataIsList?\"list of\"} ${returnDataName}[/code])\n## [br][b]@return[/b] [code]IoGame.RequestCommand[/code]\n## [br]\n## [br][b]Example Code[/b]\n## [codeblock]\n## # ${bizDataComment}\n## var ${bizDataName}: ${bizDataType} = ...\n##\n## ${actionSimpleName}.of_${actionMethodName}(${bizDataName}, func(result: IoGame.ResponseResult):\n##     # ${returnComment}\n##     ${exampleCode}\n## ).on_error(func(result: IoGame.ResponseResult):\n##     var error_code := result.get_response_status()\n##     print(\"error_code: \", error_code)\n##     print(\"error_info: \", result.get_error_info())\n## )\n## [/codeblock]\nstatic func of_${actionMethodName}(${bizDataName}: ${bizDataType}, callback: Callable) -> IoGame.RequestCommand:\n<% if (internalBizDataType) { %>\n\treturn IoGame.RequestCommand.of_${sdkMethodName!}(_${memberCmdName}, ${bizDataName}).on_callback(callback).execute()\n<% } else if (bizDataTypeIsList) { %>\n\tvar _message := IoGame.Proto.ByteValueList.new()\n\tfor _value in ${bizDataName}:\n\t\t_message.add_values(_value.to_bytes())\n\tvar _request := IoGame.RequestCommand.of(_${memberCmdName}, _message.to_bytes()).on_callback(callback)\n\t_request.data_source = ${bizDataName}\n\treturn _request.execute()\n<% } else { %>\n\tvar _data := ${bizDataName}.to_bytes()\n\tvar _request := IoGame.RequestCommand.of(_${memberCmdName}, _data).on_callback(callback)\n\t_request.data_source = ${bizDataName}\n\treturn _request.execute()\n<% } %>\n\n\n## ${methodComment}[br]\n##\n## [br][b]@param ${bizDataName}:[/b] ${bizDataComment}\n## [br][b]@return[/b] [code]IoGame.ResponseResult[/code]\n## [br]\n## [br][b]Example Code[/b]\n## [codeblock]\n## # ${bizDataComment}\n## var ${bizDataName}: ${bizDataType} = ...\n##\n## var result := await ${actionSimpleName}.of_await_${actionMethodName}(${bizDataName})\n##\n## if result.success():\n##     # ${returnComment}\n##     ${exampleCode}\n## else:\n##     var error_code := result.get_response_status()\n##     print(\"error_code: \", error_code)\n##     print(\"error_info: \", result.get_error_info())\n## [/codeblock]\nstatic func of_await_${actionMethodName}(${bizDataName}: ${bizDataType}) -> IoGame.ResponseResult:\n<% if (internalBizDataType) { %>\n\treturn await IoGame.RequestCommand.of_await_${sdkMethodName!}(_${memberCmdName}, ${bizDataName})\n<% } else if (bizDataTypeIsList) { %>\n\tvar _message := IoGame.Proto.ByteValueList.new()\n\tfor _value in ${bizDataName}:\n\t\t_message.add_values(_value.to_bytes())\n\tvar _request := IoGame.RequestCommand.of(_${memberCmdName}, _message.to_bytes())\n\t_request.data_source = ${bizDataName}\n\treturn await IoGame.RequestCommand.of_await_request_command(_request)\n<% } else { %>\n\tvar _data := ${bizDataName}.to_bytes()\n\tvar _request := IoGame.RequestCommand.of(_${memberCmdName}, _data)\n\t_request.data_source = ${bizDataName}\n\treturn await IoGame.RequestCommand.of_await_request_command(_request)\n<% } %>\n\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/action_method_no_param.txt",
    "content": "## ${methodComment}[br]\n##\n## [br][b]@param callback:[/b] ${returnComment} (returnType: [code]${returnDataIsList?\"list of \"} ${returnDataName}[/code])\n## [br][b]@return[/b] [code]IoGame.RequestCommand[/code]\n## [br]\n## [br][b]Example Code[/b]\n## [codeblock]\n## ${actionSimpleName}.of_${actionMethodName}(func(result: IoGame.ResponseResult):\n##     # ${returnComment}\n##     ${exampleCode}\n## })\n## [/codeblock]\nstatic func of_${actionMethodName}(callback: Callable) -> IoGame.RequestCommand:\n\treturn IoGame.RequestCommand.of_empty(_${memberCmdName}).on_callback(callback).execute()\n\n\n## ${methodComment}[br]\n##\n## [br][b]@return[/b] [code]IoGame.ResponseResult[/code]\n## [br]\n## [br][b]Example Code[/b]\n## [codeblock]\n## var result := await ${actionSimpleName}.of_await_${actionMethodName}()\n##\n## if result.success():\n##     # ${returnComment}\n##     ${exampleCode}\n## else:\n##     var error_code := result.get_response_status()\n##     print(\"error_code: \", error_code)\n##     print(\"error_info: \", result.get_error_info())\nstatic func of_await_${actionMethodName}() -> IoGame.ResponseResult:\n\treturn await IoGame.RequestCommand.of_await_empty(_${memberCmdName})\n\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/action_method_result_example.txt",
    "content": "<%\nvar resultMethodName;\nvar variateName = \"_value :=\";\nif (returnDataTypeIsInternal) {\n    // see GDScriptTypeMappingDocument.extractedInitTypeMapping(), TypeMappingRecord\n    if (returnDataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar ${variateName} result.${resultMethodName}\n<%\n} else {\n    // see TypeScriptMappingDocument.getTypeMappingRecord, ActionMethodDocument.extractedReturnInfo\n    if (returnDataIsList) {\n        resultMethodName = resultMethodListTypeName;\n        variateName = \"_value: Array =\";\n    } else {\n        resultMethodName = resultMethodTypeName + \" as \" + returnDataActualTypeName;\n    }\n%>\nvar ${variateName} result.${resultMethodName}\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/action_method_void.txt",
    "content": "## ${methodComment}[br]\n##\n## [br][b]@param ${bizDataName}:[/b] ${bizDataComment}\n## [br][b]@return[/b] [code]IoGame.RequestCommand[/code]\n## [br]\n## [br][b]Example Code[/b]\n## [codeblock]\n## # ${bizDataComment}\n## var ${bizDataName}: ${bizDataType} = ...\n## ${actionSimpleName}.of${actionMethodName}(${bizDataName});\n## [/codeblock]\nstatic func of_${actionMethodName}(${bizDataName}: ${bizDataType}) -> IoGame.RequestCommand:\n<% if (internalBizDataType) { %>\n\treturn IoGame.RequestCommand.of_${sdkMethodName!}(_${memberCmdName}, ${bizDataName}).execute()\n<% } else if (bizDataTypeIsList) { %>\n\tvar _message := IoGame.Proto.ByteValueList.new()\n\tfor _value in ${bizDataName}:\n\t\t_message.add_values(_value.to_bytes())\n\n\tvar _request := IoGame.RequestCommand.of_${sdkMethodName}(_${memberCmdName}, _message.to_bytes())\n\t_request.data_source = ${bizDataName}\n\treturn _request.execute()\n<% } else { %>\n\tvar _data := ${bizDataName}.to_bytes()\n\tvar _request = IoGame.RequestCommand.of(_${memberCmdName}, _data)\n\t_request.data_source = ${bizDataName}\n\treturn _request.execute()\n<% } %>\n\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/action_method_void_no_param.txt",
    "content": "## ${methodComment}[br]\n##\n## [br][b]@return[/b] [code]IoGame.RequestCommand void[/code]\n## [br]\n## [br][b]Example Code[/b]\n## [codeblock]\n## ${actionSimpleName}.of_${actionMethodName}();\n## [/codeblock]\nstatic func of_${actionMethodName}() -> IoGame.RequestCommand:\n\treturn IoGame.RequestCommand.of_empty(_${memberCmdName}).execute()\n\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/broadcast_action.txt",
    "content": "# ${generateTime}\n# ${ioGameHome}\n\n## Broadcast Listener. cn:广播监听[br]\n## https://github.com/iohao/ioGame\nclass_name Listener\nextends RefCounted\n\n${imports}\n\n<%for(o in broadcastDocumentList) {%>\nstatic var _${o.cmdMethodName}_${o.cmd}_${o.subCmd}: int = IoGame.CmdKit.mapping_broadcast(${o.cmdMerge}, \"${o.methodDescription}\")\n<%}%>\n\n\n<%for(o in broadcastDocumentList) {%>\n## ${o.methodDescription}[br]\n##\n## [br][b]@param callback:[/b] ${o.dataDescription} (returnType: ${o.dataIsList?\"list of\"} [code]${o.bizDataType}[/code])\n## [br]\n## [br][b]Example Code[/b]\n## [codeblock]\n## Listener.listen_${o.methodName}(func(result: IoGame.ResponseResult):\n##     # ${o.dataDescription!\"broadcast notification\"}\n##     ${o.exampleCode!}\n## )\n## [/codeblock]\nstatic func listen_${o.methodName}(callback: Callable) -> void:\n\t# ${o.dataDescription!}\n\tIoGame.ListenCommand.of(_${o.cmdMethodName}_${o.cmd}_${o.subCmd}, callback)\n\n\n<%}%>\nstatic func listener_ioGame() -> void:\n\t# all listener\n<%for(o in broadcastDocumentList) {%>\n\tListener.listen_${o.methodName}(func(result: IoGame.ResponseResult):\n\t\tvar _merge_title := IoGame.CmdKit.to_string_merge(result.get_cmd_merge())\n\t\tvar _title := IoGame.CmdKit.get_broadcast_title(result.get_cmd_merge())\n    <% if (o.exampleCodeAction == null) { %>\n\t\tvar _format := \"[%s], [broadcast_title: %s]\" % [_merge_title, _title]\n\t\tIoGame.IoGameSetting.game_console.log(_format)\n    <% } else { %>\n\t\t${o.exampleCodeAction!}\n\t\tvar _format := \"[%s], [broadcast_title: %s], %s\" % [_merge_title, _title, _value]\n\t\tIoGame.IoGameSetting.game_console.log(_format)\n    <%}%>\n\t)\n\n<%}%>\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/broadcast_action_example.txt",
    "content": "<%\nvar resultMethodName;\nvar variateName = \"_value :=\";\nif (dataTypeIsInternal) {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar ${variateName} result.${resultMethodName}\n<%\n} else {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n        variateName = \"_value: Array =\";\n    } else {\n        resultMethodName = resultMethodTypeName + \" as \" + dataActualTypeName;\n    }\n%>\nvar ${variateName} result.${resultMethodName}\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/broadcast_action_example_action.txt",
    "content": "<%\nvar resultMethodName;\nif (dataTypeIsInternal) {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar _value := result.${resultMethodName}\n<%\n} else {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nvar _value := result.${resultMethodName}\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/gdscript/game_code.txt",
    "content": "# ${generateTime}\n# ${ioGameHome}\n\n## GameCode、ErrorCode. cn:游戏错误码[br]\n## https://github.com/iohao/ioGame\nclass_name GameCode\nextends RefCounted\n\n<%for(o in errorCodeDocumentList) {%>\n## ${o.description}\nstatic var ${o.name}: int = IoGame.CmdKit.mapping_error_code(${o.value}, \"${o.description!}\")\n<%}%>\n\nstatic func init() -> void:\n\t# trigger errorCodeMapping init. cn:调用一次方法，触发错误码的初始化操作。\n\tpass\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/action.txt",
    "content": "${generateTime}\n${ioGameHome}\n${imports}\nimport {toBinary} from \"@bufbuild/protobuf\";\nimport {CmdKit, RequestCommand, ResponseResult} from \"iohao-sdk\";\n\n/**\n * ${ActionComment}\n * @author https://github.com/iohao/ioGame\n */\nexport class ${ActionName} {\n<%for(o in actionMemberCmdDocumentList) {%>\n    ${publicActionCmdName} static readonly ${o.memberName}: number = CmdKit.mappingRequest(${o.cmdMerge}, \"${o.comment}\");\n<%}%>\n<%for(o in methodCodeList) {%>\n${o}\n<%}%>\n}"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/action_method.txt",
    "content": "\n    /**\n     * ${methodComment}\n     * @param ${bizDataName} ${bizDataComment}\n     * @param callback ${returnComment} (returnType: ${returnDataIsList?\"list of \"}{@link ${returnDataName}})\n     * @returns {@link RequestCommand} request command\n     * @example\n     * // ${bizDataComment}\n     * const ${bizDataName}: ${bizDataType} = ...;\n     * ${actionSimpleName}.of${actionMethodName}(${bizDataName}, result => {\n     *     // ioGame: your biz code.\n     *     // ${returnComment}\n     *     ${exampleCode}\n     *     result.log(value);\n     * });\n     */\n    static of${actionMethodName}(${bizDataName}: ${protoPrefix}${bizDataType}, callback: (result: ResponseResult) => void): RequestCommand {\n    <% if (internalBizDataType) { %>\n        return RequestCommand.of${sdkMethodName!}(this.${memberCmdName}, ${bizDataName}).onCallback(callback).execute();\n    <% } else if (bizDataTypeIsList) { %>\n        const dataList = ${bizDataName}.map(o => {\n            return toBinary(${protoPrefix}${actualTypeName}Schema, o);\n        });\n        const requestCommand = RequestCommand.of${sdkMethodName}(this.${memberCmdName}, dataList).onCallback(callback);\n        requestCommand.dataSource = ${bizDataName};\n        return requestCommand.execute();\n    <% } else { %>\n        const data = toBinary(${protoPrefix}${bizDataType}Schema, ${bizDataName});\n        const requestCommand = RequestCommand.of(this.${memberCmdName}, data).onCallback(callback);\n        requestCommand.dataSource = ${bizDataName};\n        return requestCommand.execute();\n    <% } %>\n    }\n\n    /**\n     * ${methodComment}\n     * @param ${bizDataName} ${bizDataComment}\n     * @returns {@link ResponseResult} ${returnComment} (returnType: ${returnDataIsList?\"list of \"}{@link ${returnDataName}})\n     * @example\n     * // ${bizDataComment}\n     * const ${bizDataName}: ${bizDataType} = ...;\n     * const result = await ${actionSimpleName}.ofAwait${actionMethodName}(${bizDataName});\n     * // ioGame: your biz code.\n     * // ${returnComment}\n     * ${exampleCode}\n     * result.log(value);\n     */\n    static async ofAwait${actionMethodName}(${bizDataName}: ${protoPrefix}${bizDataType}): Promise<ResponseResult> {\n    <% if (internalBizDataType) { %>\n        return await RequestCommand.ofAwait${sdkMethodName!}(this.${memberCmdName}, ${bizDataName});\n    <% } else if (bizDataTypeIsList) { %>\n        const dataList = ${bizDataName}.map(o => {\n            return toBinary(${protoPrefix}${actualTypeName}Schema, o);\n        });\n\n        const requestCommand = RequestCommand.of${sdkMethodName}(this.${memberCmdName}, dataList);\n        requestCommand.dataSource = ${bizDataName};\n\n        return await RequestCommand.ofAwaitRequestCommand(requestCommand);\n    <% } else { %>\n        const data = toBinary(${protoPrefix}${bizDataType}Schema, ${bizDataName});\n        const requestCommand = RequestCommand.of(this.${memberCmdName}, data);\n        requestCommand.dataSource = ${bizDataName};\n        return await RequestCommand.ofAwaitRequestCommand(requestCommand);\n    <% } %>\n    }"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/action_method_no_param.txt",
    "content": "\n    /**\n     * ${methodComment}\n     * @param callback ${returnComment} (returnType: ${returnDataIsList?\"list of \"}{@link ${returnDataName}})\n     * @returns {@link RequestCommand} request command\n     * @example\n     * ${actionSimpleName}.of${actionMethodName}(result => {\n     *     // ioGame: your biz code.\n     *     result.log(\"hello ioGame!\");\n     *     // ${returnComment}\n     *     ${exampleCode}\n     *     result.log(value);\n     * });\n     */\n    static of${actionMethodName}(callback: (result: ResponseResult) => void): RequestCommand {\n        return RequestCommand.of${sdkMethodName!}(this.${memberCmdName}).onCallback(callback).execute();\n    }\n\n    /**\n     * ${methodComment}\n     * @returns {@link ResponseResult} [${returnComment} : ${returnDataIsList?\"list of \"}{@link ${returnDataName}}]\n     * @example\n     * const result = await ${actionSimpleName}.ofAwait${actionMethodName}();\n     * // ioGame: your biz code.\n     * result.log(\"hello ioGame!\");\n     * // ${returnComment}\n     * ${exampleCode}\n     * result.log(value);\n     */\n    static async ofAwait${actionMethodName}(): Promise<ResponseResult> {\n        return await RequestCommand.ofAwait${sdkMethodName!}(this.${memberCmdName});\n    }"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/action_method_result_example.txt",
    "content": "<%\nvar resultMethodName;\nif (returnDataTypeIsInternal) {\n    // see TypeScriptMappingDocument.extractedInitTypeMapping, TypeMappingRecord\n    if (returnDataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nconst value = result.${resultMethodName}();\n<%\n} else {\n    // see TypeScriptMappingDocument.getTypeMappingRecord, ActionMethodDocument.extractedReturnInfo\n    if (returnDataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nconst value = result.${resultMethodName}(${returnDataActualTypeName}Schema);\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/action_method_void.txt",
    "content": "\n    /**\n     * ${methodComment}\n     * @param ${bizDataName} ${bizDataComment}。\n     * @returns RequestCommand void\n     * @example\n     * // ${bizDataComment}\n     * const ${bizDataName}: ${bizDataType} = ...;\n     * ${actionSimpleName}.of${actionMethodName}(${bizDataName});\n     */\n    static of${actionMethodName}(${bizDataName}: ${protoPrefix}${bizDataType}): RequestCommand {\n    <% if (internalBizDataType) { %>\n        return RequestCommand.of${sdkMethodName!}(this.${memberCmdName}, ${bizDataName}).execute();\n    <% } else if (bizDataTypeIsList) { %>\n        const dataList = ${bizDataName}.map(o => {\n            return toBinary(${protoPrefix}${actualTypeName}Schema, o);\n        });\n\n        return RequestCommand.of${sdkMethodName}(this.${memberCmdName}, dataList).execute();\n    <% } else { %>\n        const data = toBinary(${protoPrefix}${bizDataType}Schema, ${bizDataName});\n        const requestCommand = RequestCommand.of(this.${memberCmdName}, data);\n        requestCommand.dataSource = ${bizDataName};\n        return requestCommand.execute();\n    <% } %>\n    }"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/action_method_void_no_param.txt",
    "content": "\n    /**\n     * ${methodComment}\n     * @returns RequestCommand void\n     * @example\n     * ${actionSimpleName}.of${actionMethodName}();\n     */\n    static of${actionMethodName}(): RequestCommand {\n        return RequestCommand.of${sdkMethodName!}(this.${memberCmdName}).execute();\n    }"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/broadcast_action.txt",
    "content": "${generateTime}\n${ioGameHome}\n${imports}\nimport {CmdKit, ListenCommand, ResponseResult} from \"iohao-sdk\";\n\n/**\n * Broadcast Listener；广播监听；\n * @author https://github.com/iohao/ioGame\n */\nexport class Listener {\n<%for(o in broadcastDocumentList) {%>\n    private static readonly ${o.cmdMethodName}_${o.cmd}_${o.subCmd}: number = CmdKit.mappingBroadcast(${o.cmdMerge},'${o.methodDescription!}');\n<%}%>\n<%for(o in broadcastDocumentList) {%>\n\n    /**\n     * ${o.methodDescription}\n     * @param callback ${o.dataDescription}, ${o.dataIsList?\"list of \"}{@link ${o.bizDataType}}\n     * @example\n     * Listener.listen${o.methodName}(result => {\n     *     // ${o.dataDescription!\"broadcast notification\"}\n     *     ${o.exampleCode!}\n     * });\n     */\n    static listen${o.methodName}(callback: (result: ResponseResult) => void) {\n        // ${o.dataDescription!}\n        ListenCommand.of(this.${o.cmdMethodName}_${o.cmd}_${o.subCmd}, callback);\n    }\n<%}%>\n\n    static listener_ioGame() {\n        // all listener\n    <%for(o in broadcastDocumentList) {%>\n\n        Listener.listen${o.methodName}(result => {\n            const mergeTitle = CmdKit.toString(result.getCmdMerge());\n            const title = CmdKit.getBroadcastTitle(result.getCmdMerge());\n\n        <% if (o.exampleCodeAction == null) { %>\n            console.log(mergeTitle, title);\n        <% } else { %>\n            ${o.exampleCodeAction!}\n            console.log(mergeTitle, title, value);\n        <%}%>\n        });\n    <%}%>\n    }\n}\n\n"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/broadcast_action_example.txt",
    "content": "<%\nvar resultMethodName;\nif (dataTypeIsInternal) {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nconst value = result.${resultMethodName}();\n<%\n} else {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nconst value = result.${resultMethodName}(${dataActualTypeName}Schema);\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/broadcast_action_example_action.txt",
    "content": "<%\nvar resultMethodName;\nif (dataTypeIsInternal) {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nconst value = result.${resultMethodName}();\n<%\n} else {\n    if (dataIsList) {\n        resultMethodName = resultMethodListTypeName;\n    } else {\n        resultMethodName = resultMethodTypeName;\n    }\n%>\nconst value = result.${resultMethodName}(${dataActualTypeName}Schema);\n<%}%>"
  },
  {
    "path": "widget/generate-code/src/main/resources/generate/ts/game_code.txt",
    "content": "${generateTime}\n${ioGameHome}\nimport {CmdKit} from \"iohao-sdk\";\n\n/**\n * GameCode、ErrorCode. 游戏错误码\n * @author https://github.com/iohao/ioGame\n */\nexport class GameCode {\n<%for(o in errorCodeDocumentList) {%>\n    /** ${o.description} */\n    static ${o.name}: number = CmdKit.mappingErrorCode(${o.value}, \"${o.description!}\");\n<%}%>\n\n    static init() {\n        // trigger errorCodeMapping init.\n        // 调用一次方法，触发错误码的初始化操作。\n    }\n}"
  },
  {
    "path": "widget/light-client/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.iohao.game</groupId>\n        <artifactId>ioGame</artifactId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n\n    <artifactId>light-client</artifactId>\n    <name>light-client for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>external-netty</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- 模拟客户端 https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->\n        <dependency>\n            <groupId>org.java-websocket</groupId>\n            <artifactId>Java-WebSocket</artifactId>\n            <version>1.5.3</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/AbstractInputCommandRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.external.client.command.CallbackDelegate;\nimport com.iohao.game.external.client.command.InputCommand;\nimport com.iohao.game.external.client.command.ListenCommand;\nimport com.iohao.game.external.client.command.RequestCommand;\nimport com.iohao.game.external.client.user.ClientUser;\nimport com.iohao.game.external.client.user.ClientUserChannel;\nimport com.iohao.game.external.client.user.ClientUserInputCommands;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-10\n */\n@FieldDefaults(level = AccessLevel.PROTECTED)\npublic abstract class AbstractInputCommandRegion implements InputCommandRegion {\n    /** 模块域 */\n    InputCommandCreate inputCommandCreate = new InputCommandCreate();\n    ClientUser clientUser;\n    long userId;\n\n    @Override\n    public void setClientUser(ClientUser clientUser) {\n        this.userId = clientUser.getUserId();\n        this.clientUser = clientUser;\n\n        ClientUserInputCommands clientUserInputCommands = clientUser.getClientUserInputCommands();\n        inputCommandCreate.setClientUserInputCommands(clientUserInputCommands);\n    }\n\n    /**\n     * 创建模拟命令\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    protected InputCommand ofCommand(int subCmd) {\n        return this.inputCommandCreate.ofInputCommand(subCmd);\n    }\n\n    /**\n     * 创建模拟命令，在使用命令时需要在控制台输入 long 类型的请求参数\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    protected InputCommand ofCommandLong(int subCmd) {\n        return this.inputCommandCreate.ofInputCommandLong(subCmd);\n    }\n\n    /**\n     * 创建模拟命令，在使用命令时需要在控制台输入 long 类型的 userId 请求参数\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    protected InputCommand ofCommandUserId(int subCmd) {\n        return this.inputCommandCreate.ofInputCommandUserId(subCmd);\n    }\n\n    /**\n     * 创建模拟命令，在使用命令时需要在控制台输入 int 类型的请求参数\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    protected InputCommand ofCommandInt(int subCmd) {\n        return inputCommandCreate.ofInputCommandInt(subCmd);\n    }\n\n    /**\n     * 创建模拟命令，在使用命令时需要在控制台输入 String 类型的请求参数\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    protected InputCommand ofCommandString(int subCmd) {\n        return this.inputCommandCreate.ofInputCommandString(subCmd);\n    }\n\n    protected void ofListen(CallbackDelegate callback, int subCmd, String title) {\n        this.ofListen(subCmd)\n                .setCallback(callback)\n                .setTitle(title);\n    }\n\n    protected void ofListen(CallbackDelegate callback, CmdInfo cmd, String title) {\n        this.ofListen(cmd.getSubCmd())\n                .setCallback(callback)\n                .setTitle(title);\n    }\n\n    private ListenCommand ofListen(int subCmd) {\n        CmdInfo cmdInfo = inputCommandCreate.ofCmdInfo(subCmd);\n        ListenCommand listenCommand = new ListenCommand(cmdInfo);\n\n        ClientUserChannel clientUserChannel = this.clientUser.getClientUserChannel();\n        clientUserChannel.addListen(listenCommand);\n\n        return listenCommand;\n    }\n\n    /**\n     * 创建请求命令执行\n     *\n     * @param subCmd 子路由\n     * @return 请求命令执行\n     */\n    public RequestCommand ofRequestCommand(int subCmd) {\n        CmdInfo cmdInfo = this.inputCommandCreate.ofCmdInfo(subCmd);\n        return this.ofRequestCommand(cmdInfo);\n    }\n\n    /**\n     * 创建请求命令执行\n     *\n     * @param cmdInfo 路由信息\n     * @return 请求命令执行\n     */\n    public RequestCommand ofRequestCommand(CmdInfo cmdInfo) {\n        ClientUserInputCommands clientUserInputCommands = this.inputCommandCreate.clientUserInputCommands;\n        return clientUserInputCommands.ofRequestCommand(cmdInfo);\n    }\n\n    public void executeCommand(int subCmd) {\n        this.ofRequestCommand(subCmd).execute();\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/ClientConnectOption.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client;\n\nimport com.iohao.game.external.client.user.ClientUser;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.net.InetSocketAddress;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-04\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ClientConnectOption {\n    String wsUrl;\n    InetSocketAddress socketAddress;\n    ClientUser clientUser;\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/InputCommandCreate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.protocol.wrapper.IntValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.LongValue;\nimport com.iohao.game.action.skeleton.protocol.wrapper.StringValue;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.external.client.command.InputCommand;\nimport com.iohao.game.external.client.command.RequestDataDelegate;\nimport com.iohao.game.external.client.kit.AssertKit;\nimport com.iohao.game.external.client.kit.ClientKit;\nimport com.iohao.game.external.client.kit.ClientUserConfigs;\nimport com.iohao.game.external.client.kit.ScannerKit;\nimport com.iohao.game.external.client.user.ClientUserInputCommands;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.Objects;\n\n/**\n * 模块输入命令域\n *\n * @author 渔民小镇\n * @date 2023-07-09\n */\n@Slf4j\n@Setter\n@Getter\n@FieldDefaults(level = AccessLevel.PUBLIC)\npublic class InputCommandCreate {\n    int cmd = -1;\n    /** true 相同路由的 InputCommand 只能存在一个 */\n    boolean uniqueInputCommand = ClientUserConfigs.uniqueInputCommand;\n    /** 模块描述的前缀 */\n    String cmdName = \"\";\n\n    ClientUserInputCommands clientUserInputCommands;\n\n    public CmdInfo ofCmdInfo(int subCmd) {\n        AssertKit.assertTrueThrow(cmd < 0, \"cmd 不能小于 0\");\n        return CmdInfo.of(cmd, subCmd);\n    }\n\n    public InputCommand getInputCommand(int subCmd) {\n        CmdInfo cmdInfo = ofCmdInfo(subCmd);\n        InputCommand inputCommand = clientUserInputCommands.getInputCommand(cmdInfo);\n        Objects.requireNonNull(inputCommand, \"没有对应的请求配置\");\n        return inputCommand;\n    }\n\n    /**\n     * 创建模拟命令\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    public InputCommand ofInputCommand(int subCmd) {\n        return ofInputCommand(subCmd, null);\n    }\n\n    private InputCommand ofInputCommand(int subCmd, RequestDataDelegate requestData) {\n\n        CmdInfo cmdInfo = ofCmdInfo(subCmd);\n\n        // 唯一性路由命令检测，先检查命令是否存在\n        extractedChecked(cmdInfo);\n\n        return clientUserInputCommands.ofCommand(cmdInfo)\n                .setCmdName(this.cmdName)\n                .setRequestData(requestData);\n    }\n\n    private void extractedChecked(CmdInfo cmdInfo) {\n        if (uniqueInputCommand) {\n            var inputName = ClientKit.toInputName(cmdInfo);\n            InputCommand inputCommand = clientUserInputCommands.getInputCommand(inputName);\n            if (Objects.nonNull(inputCommand)) {\n                // 存在重复的路由命令\n                ThrowKit.ofRuntimeException(\"There are duplicate routing commands : \" + cmdInfo);\n            }\n        }\n    }\n\n    /**\n     * 创建模拟命令，在使用命令时需要在控制台输入 long 类型的请求参数\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    public InputCommand ofInputCommandLong(int subCmd) {\n        RequestDataDelegate requestData = nextParamLong(\"参数\");\n        return ofInputCommand(subCmd, requestData);\n    }\n\n    /**\n     * 创建模拟命令，在使用命令时需要在控制台输入 long 类型的 userId 请求参数\n     *\n     * @param subCmd 子路由\n     * @return InputCommand\n     */\n    public InputCommand ofInputCommandUserId(int subCmd) {\n        RequestDataDelegate requestData = nextParamLong(\"对方的 userId\");\n        return ofInputCommand(subCmd, requestData);\n    }\n\n    public RequestDataDelegate nextParamLong(String paramTips) {\n        return () -> {\n            log.info(\"请输入{} | 参数类型 : long.class\", paramTips);\n\n            long longValue = ScannerKit.nextLong();\n            return LongValue.of(longValue);\n        };\n    }\n\n    public InputCommand ofInputCommandInt(int subCmd) {\n        RequestDataDelegate requestData = nextParamInt(\"参数\");\n        return ofInputCommand(subCmd, requestData);\n    }\n\n    public RequestDataDelegate nextParamInt(String paramTips) {\n        return () -> {\n            String info = \"请输入{} | 参数类型 : int.class\";\n            log.info(info, paramTips);\n\n            int intValue = ScannerKit.nextInt();\n            return IntValue.of(intValue);\n        };\n    }\n\n    public InputCommand ofInputCommandString(int subCmd) {\n        RequestDataDelegate requestData = nextParamString(\"参数\");\n        return ofInputCommand(subCmd, requestData);\n    }\n\n    public RequestDataDelegate nextParamString(String paramTips) {\n        return () -> {\n            String info = \"请输入{} | 参数类型 : String.class\";\n            log.info(info, paramTips);\n            String s = ScannerKit.nextLine();\n            Objects.requireNonNull(s);\n\n            return StringValue.of(s);\n        };\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/InputCommandRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client;\n\nimport com.iohao.game.external.client.user.ClientUser;\n\n/**\n * 命令域\n *\n * @author 渔民小镇\n * @date 2023-07-09\n */\npublic interface InputCommandRegion {\n    /**\n     * 初始化模拟命令\n     */\n    void initInputCommand();\n\n    /**\n     * 设置 clientUser\n     * <pre>\n     *     会在客户端启动时，自动赋值\n     * </pre>\n     *\n     * @param clientUser clientUser\n     */\n    void setClientUser(ClientUser clientUser);\n\n    /**\n     * 玩家登录成功后的回调\n     * <pre>\n     *     see {@link ClientUser#callbackInputCommandRegion()}\n     * </pre>\n     *\n     */\n    default void loginSuccessCallback() {\n\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/command/CallbackDelegate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.command;\n\n/**\n * @author 渔民小镇\n * @date 2023-09-19\n */\n@FunctionalInterface\npublic interface CallbackDelegate {\n    void callback(CommandResult result);\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/command/CommandResult.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.command;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.wrapper.*;\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 回调结果\n *\n * @author 渔民小镇\n * @date 2023-07-08\n */\n@FieldDefaults(level = AccessLevel.PACKAGE)\npublic class CommandResult {\n    final BarMessage message;\n    /** 业务对象 */\n    Object value;\n\n    public CommandResult(BarMessage message) {\n        this.message = message;\n    }\n\n    public ExternalMessage getExternalMessage() {\n        return this.message.getHeadMetadata().getExternalMessage();\n    }\n\n    public int getMsgId() {\n        return message.getHeadMetadata().getMsgId();\n    }\n\n    public CmdInfo getCmdInfo() {\n        return message.getHeadMetadata().getCmdInfo();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getValue(Class<? extends T> clazz) {\n        byte[] data = this.message.getData();\n\n        if (Objects.isNull(this.value)) {\n            this.value = DataCodecKit.decode(data, clazz);\n        }\n\n        return (T) value;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> List<T> listValue(Class<? extends T> clazz) {\n        return (List<T>) this.getValue(ByteValueList.class)\n                .values\n                .stream()\n                .map(v -> DataCodecKit.decode(v, clazz))\n                .toList();\n    }\n\n    public String getString() {\n        return this.getValue(StringValue.class).value;\n    }\n\n    public List<String> listString() {\n        return this.getValue(StringValueList.class).values;\n    }\n\n    public int getInt() {\n        return this.getValue(IntValue.class).value;\n    }\n\n    public List<Integer> listInt() {\n        return this.getValue(IntValueList.class).values;\n    }\n\n    public long getLong() {\n        return this.getValue(LongValue.class).value;\n    }\n\n    public List<Long> listLong() {\n        return this.getValue(LongValueList.class).values;\n    }\n\n    public boolean getBoolean() {\n        return this.getValue(BoolValue.class).value;\n    }\n\n    public List<Boolean> listBoolean() {\n        return this.getValue(BoolValueList.class).values;\n    }\n\n    @Override\n    public String toString() {\n\n        CmdInfo cmdInfo = getCmdInfo();\n\n        return StrKit.format(\"msgId:{} - {} \\n{}\"\n                , getMsgId()\n                , CmdKit.mergeToShort(cmdInfo.getCmdMerge())\n                , value);\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/command/InputCommand.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.command;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.external.client.kit.ClientKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 模拟命令\n * example:\n * <pre>{@code\n *         ofCommand(DemoCmd.here).setTitle(\"here\").setRequestData(() -> {\n *             YourMsg msg = ...\n *             return msg;\n *         }).callback(result -> {\n *              HelloReq value = result.getValue(HelloReq.class);\n *              log.info(\"value : {}\", value);\n *          });\n *\n *         ofCommand(DemoCmd.list).setTitle(\"list\").callback(result -> {\n *             // 得到 list 数据\n *             List<HelloReq> list = result.listValue(HelloReq.class);\n *             log.info(\"list : {}\", list);\n *         });\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-07-08\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class InputCommand {\n\n    final String inputName;\n    final CmdInfo cmdInfo;\n\n    /** 模拟请求命令的描述 */\n    String title = \"... ...\";\n    /** 描述的前缀 */\n    String cmdName = \"\";\n\n    /** 请求参数 */\n    RequestDataDelegate requestData;\n    /** 回调接口 */\n    @Setter(AccessLevel.PRIVATE)\n    CallbackDelegate callback;\n\n    public InputCommand(CmdInfo cmdInfo) {\n        this.inputName = ClientKit.toInputName(cmdInfo);\n        this.cmdInfo = cmdInfo;\n    }\n\n    public InputCommand setRequestData(RequestDataDelegate requestData) {\n        this.requestData = requestData;\n        return this;\n    }\n\n    public InputCommand callback(CallbackDelegate callback) {\n        this.callback = callback;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        if (StrKit.isEmpty(cmdName)) {\n            var format = \"%s    :    %s\";\n            return String.format(format, inputName, title);\n        }\n\n        var format = \"%s    :    [%s] - %s\";\n        return String.format(format, inputName, cmdName, title);\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/command/ListenCommand.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.command;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.external.client.kit.ClientKit;\nimport com.iohao.game.external.client.user.ClientUserChannel;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 广播监听\n *\n * @author 渔民小镇\n * @date 2023-07-09\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ListenCommand {\n    final CmdInfo cmdInfo;\n    String title;\n    CallbackDelegate callback;\n    ClientUserChannel clientUserChannel;\n\n    public ListenCommand(CmdInfo cmdInfo) {\n        this.cmdInfo = cmdInfo;\n    }\n\n    public void listen() {\n        clientUserChannel.addListen(this);\n    }\n\n    @Override\n    public String toString() {\n        String inputName = ClientKit.toInputName(this.cmdInfo);\n        return inputName + \"    :    \" + title;\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/command/RequestCommand.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.command;\n\nimport com.iohao.game.external.client.user.ClientUserChannel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\n\n/**\n * 请求命令执行。用于请求服务器的命令，业务数据需要在调用 request 方法时传入。\n *\n * @author 渔民小镇\n * @date 2023-07-14\n */\n@Getter\n@Setter\n@Accessors(chain = true)\npublic class RequestCommand {\n    ClientUserChannel clientUserChannel;\n    int cmdMerge;\n    String title = \"... ...\";\n    /** 请求参数 */\n    RequestDataDelegate requestData;\n    /** 响应回调 */\n    CallbackDelegate callback;\n\n    /**\n     * 执行请求命令\n     */\n    public void execute() {\n        clientUserChannel.execute(this);\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/command/RequestDataDelegate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.command;\n\n/**\n * @author 渔民小镇\n * @date 2023-09-19\n */\n@FunctionalInterface\npublic interface RequestDataDelegate  {\n    /**\n     * 创建请求参数\n     *\n     * @return 请求参数\n     */\n    Object createRequestData();\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientConnect.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.join;\n\nimport com.iohao.game.external.client.ClientConnectOption;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-07\n */\npublic interface ClientConnect {\n    void connect(ClientConnectOption option);\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientConnects.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.join;\n\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-07\n */\n@UtilityClass\npublic class ClientConnects {\n    Map<ExternalJoinEnum, ClientConnect> clientConnectMap = new HashMap<>();\n\n    static {\n        clientConnectMap.put(ExternalJoinEnum.TCP, new TcpClientStartup());\n        clientConnectMap.put(ExternalJoinEnum.WEBSOCKET, new WebSocketClientStartup());\n    }\n\n    public void put(ExternalJoinEnum joinEnum, ClientConnect connect) {\n        clientConnectMap.put(joinEnum, connect);\n    }\n\n    ClientConnect getClientConnect(ExternalJoinEnum joinEnum) {\n        return clientConnectMap.get(joinEnum);\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientRunOne.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.join;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.common.kit.PresentKit;\nimport com.iohao.game.common.kit.concurrent.IntervalTaskListener;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.external.client.ClientConnectOption;\nimport com.iohao.game.external.client.InputCommandRegion;\nimport com.iohao.game.external.client.user.ClientUser;\nimport com.iohao.game.external.client.user.ClientUserChannel;\nimport com.iohao.game.external.client.user.ClientUsers;\nimport com.iohao.game.external.client.user.DefaultClientUser;\nimport com.iohao.game.external.core.config.ExternalGlobalConfig;\nimport com.iohao.game.external.core.config.ExternalJoinEnum;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.message.ExternalMessageCmdCode;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.net.InetSocketAddress;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-04\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Slf4j(topic = IoGameLogName.CommonStdout)\npublic final class ClientRunOne {\n    List<InputCommandRegion> inputCommandRegions;\n    ClientUser clientUser;\n\n    /** 服务器连接端口 */\n    int connectPort = ExternalGlobalConfig.externalPort;\n    /** 连接 ip */\n    String connectAddress = \"127.0.0.1\";\n    /** websocketPath , default : /websocket */\n    String websocketPath = ExternalGlobalConfig.CoreOption.websocketPath;\n    String websocketVerify = \"\";\n\n    ExternalJoinEnum joinEnum = ExternalJoinEnum.WEBSOCKET;\n    ClientConnectOption option;\n\n    public void startup() {\n        if (Objects.isNull(this.clientUser)) {\n            this.clientUser = new DefaultClientUser();\n        }\n\n        Objects.requireNonNull(this.inputCommandRegions, \"请设置需要发送的请求消息\");\n\n        ClientUsers.addClientUser(clientUser);\n        clientUser.setInputCommandRegions(this.inputCommandRegions);\n\n        this.inputCommandRegions.forEach(inputCommandRegion -> {\n            inputCommandRegion.setClientUser(clientUser);\n            inputCommandRegion.initInputCommand();\n        });\n\n        ClientConnectOption option = getOption();\n\n        ClientConnect clientConnect = ClientConnects.getClientConnect(joinEnum);\n        if (Objects.isNull(clientConnect)) {\n            log.error(\"连接方式 {} 没有对应的实现类\", joinEnum);\n            return;\n        }\n\n        TaskKit.execute(() -> clientConnect.connect(option));\n\n        try {\n            log.info(\"启动成功\");\n            TimeUnit.SECONDS.sleep(1);\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 启动定时器发送心跳\n     * <pre>\n     *     该方法建议只调用一次\n     * </pre>\n     *\n     * @param idlePeriod 心跳周期（秒）\n     * @return this\n     */\n    public ClientRunOne idle(int idlePeriod) {\n        TaskKit.runInterval(new IntervalTaskListener() {\n            @Override\n            public void onUpdate() {\n                BarMessage message = ExternalCodecKit.createRequest();\n                HeadMetadata headMetadata = message.getHeadMetadata();\n                headMetadata.setCmdCode(ExternalMessageCmdCode.idle);\n\n                ClientUserChannel clientUserChannel = clientUser.getClientUserChannel();\n                clientUserChannel.writeAndFlush(message);\n            }\n\n            @Override\n            public boolean isActive() {\n                return clientUser.isActive();\n            }\n        }, idlePeriod, TimeUnit.SECONDS);\n\n        return this;\n    }\n\n    private ClientConnectOption getOption() {\n\n        if (Objects.isNull(option)) {\n            option = new ClientConnectOption();\n        }\n\n        PresentKit.ifNull(option.getWsUrl(), () -> {\n            String wsUrl = String.format(\"ws://%s:%d%s%s\", connectAddress, connectPort, websocketPath, websocketVerify);\n            option.setWsUrl(wsUrl);\n        });\n\n        PresentKit.ifNull(option.getSocketAddress(), () -> {\n            InetSocketAddress socketAddress = new InetSocketAddress(connectAddress, connectPort);\n            option.setSocketAddress(socketAddress);\n        });\n\n        PresentKit.ifNull(option.getClientUser(), () -> option.setClientUser(clientUser));\n\n        return option;\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientTcpExternalCodec.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.join;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageCodec;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2023-12-15\n */\npublic class ClientTcpExternalCodec extends MessageToMessageCodec<ByteBuf, BarMessage> {\n    @Override\n    protected void encode(ChannelHandlerContext ctx, BarMessage message, List<Object> out) throws Exception {\n        /*\n         * 编码器 - 将消息发送到【游戏对外服】\n         * ResponseMessage ---> ExternalMessage ---> 字节数组\n         */\n        var externalMessage = ExternalCodecKit.convertExternalMessage(message);\n        byte[] bytes = DataCodecKit.encode(externalMessage);\n        ByteBuf buffer = ctx.alloc().buffer(bytes.length + 4);\n        buffer.writeInt(bytes.length);\n        buffer.writeBytes(bytes);\n        out.add(buffer);\n    }\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {\n        /*\n         * 解码器 - 接收【游戏对外服】的消息\n         * 字节数组 ---> ExternalMessage ---> RequestMessage\n         */\n        // 消息\n        byte[] msgBytes = new byte[msg.readInt()];\n        msg.readBytes(msgBytes);\n\n        ExternalMessage externalMessage = DataCodecKit.decode(msgBytes, ExternalMessage.class);\n        BarMessage message = ExternalCodecKit.convertRequestMessage(externalMessage);\n        //【游戏对外服】接收【游戏客户端】的消息\n        out.add(message);\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/join/TcpClientStartup.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.join;\n\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.client.ClientConnectOption;\nimport com.iohao.game.external.client.join.handler.ClientMessageHandler;\nimport com.iohao.game.external.client.user.ClientUser;\nimport com.iohao.game.external.client.user.ClientUserChannel;\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.*;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.handler.codec.LengthFieldBasedFrameDecoder;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.net.InetSocketAddress;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-05\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\nclass TcpClientStartup implements ClientConnect {\n    static int PACKAGE_MAX_SIZE = 1024 * 1024;\n\n    @Override\n    public void connect(ClientConnectOption option) {\n        ClientUser clientUser = option.getClientUser();\n        ClientMessageHandler clientMessageHandler = new ClientMessageHandler(clientUser);\n\n        EventLoopGroup group = new NioEventLoopGroup();\n        var bootstrap = new Bootstrap();\n        bootstrap.group(group)\n                .channel(NioSocketChannel.class)\n                .option(ChannelOption.TCP_NODELAY, true)\n                .handler(new ChannelInitializer<SocketChannel>() {\n                    @Override\n                    protected void initChannel(SocketChannel ch) {\n                        // 编排网关业务\n                        ChannelPipeline pipeline = ch.pipeline();\n\n                        // 数据包长度 = 长度域的值 + lengthFieldOffset + lengthFieldLength + lengthAdjustment。\n                        pipeline.addLast(new LengthFieldBasedFrameDecoder(PACKAGE_MAX_SIZE,\n                                // 长度字段的偏移量， 从 0 开始\n                                0,\n                                // 字段的长度, 如果使用的是 short ，占用2位；（消息头用的 byteBuf.writeShort 来记录长度的）\n                                // 字段的长度, 如果使用的是 int   ，占用4位；（消息头用的 byteBuf.writeInt   来记录长度的）\n                                4,\n                                // 要添加到长度字段值的补偿值：长度调整值 = 内容字段偏移量 - 长度字段偏移量 - 长度字段的字节数\n                                0,\n                                // 跳过的初始字节数： 跳过0位; (跳过消息头的 0 位长度)\n                                0));\n\n                        // 编解码\n                        pipeline.addLast(\"codec\", new ClientTcpExternalCodec());\n\n                        pipeline.addLast(clientMessageHandler);\n                    }\n                });\n\n        InetSocketAddress address = option.getSocketAddress();\n        String hostName = address.getHostName();\n        int port = address.getPort();\n        final ChannelFuture channelFuture = bootstrap.connect(hostName, port);\n\n        try {\n            Channel channel = channelFuture.sync().channel();\n\n            ClientUserChannel userChannel = clientUser.getClientUserChannel();\n            userChannel.setClientChannel(channel::writeAndFlush);\n\n            userChannel.setCloseChannel(channel::close);\n\n            clientUser.getClientUserInputCommands().start();\n\n            channel.closeFuture().await();\n        } catch (InterruptedException e) {\n            log.error(e.getMessage(), e);\n        } finally {\n            group.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/join/WebSocketClientStartup.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.join;\n\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.common.consts.IoGameLogName;\nimport com.iohao.game.external.client.ClientConnectOption;\nimport com.iohao.game.external.client.user.ClientUser;\nimport com.iohao.game.external.client.user.ClientUserChannel;\nimport com.iohao.game.external.core.message.ExternalCodecKit;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.java_websocket.client.WebSocketClient;\nimport org.java_websocket.drafts.Draft_6455;\nimport org.java_websocket.handshake.ServerHandshake;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.ByteBuffer;\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-04\n */\n@Slf4j(topic = IoGameLogName.CommonStdout)\nclass WebSocketClientStartup implements ClientConnect {\n    @Override\n    public void connect(ClientConnectOption option) {\n        ClientUser clientUser = option.getClientUser();\n        ClientUserChannel clientUserChannel = clientUser.getClientUserChannel();\n\n        String wsUrl = option.getWsUrl();\n\n        URI uri = null;\n        try {\n            uri = new URI(wsUrl);\n        } catch (URISyntaxException e) {\n            log.error(e.getMessage(), e);\n        }\n\n        // 连接游戏服务器的地址\n        WebSocketClient webSocketClient = new WebSocketClient(Objects.requireNonNull(uri), new Draft_6455()) {\n            @Override\n            public void onOpen(ServerHandshake handshakedata) {\n            }\n\n            @Override\n            public void onMessage(String s) {\n                log.info(\"onMessage : {}\", s);\n            }\n\n            @Override\n            public void onClose(int i, String s, boolean b) {\n                log.info(\"onClose : {}\", s);\n            }\n\n            @Override\n            public void onError(Exception e) {\n                log.error(e.getMessage(), e);\n            }\n\n            @Override\n            public void onMessage(ByteBuffer byteBuffer) {\n                // 接收服务器返回的消息\n                byte[] msgBytes = byteBuffer.array();\n                ExternalMessage externalMessage = DataCodecKit.decode(msgBytes, ExternalMessage.class);\n\n                BarMessage message = ExternalCodecKit.convertRequestMessage(externalMessage);\n\n                clientUserChannel.readMessage(message);\n            }\n        };\n\n        clientUserChannel.setClientChannel(message -> {\n            ExternalMessage externalMessage = ExternalCodecKit.convertExternalMessage(message);\n            byte[] bytes = DataCodecKit.encode(externalMessage);\n            webSocketClient.send(bytes);\n        });\n\n        clientUserChannel.setCloseChannel(webSocketClient::close);\n\n        clientUser.getClientUserInputCommands().start();\n\n        // 开始连接服务器\n        webSocketClient.connect();\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/join/handler/ClientMessageHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.join.handler;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.external.client.user.ClientUser;\nimport com.iohao.game.external.client.user.ClientUserChannel;\nimport com.iohao.game.external.core.message.ExternalMessage;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\n\n/**\n * @author 渔民小镇\n * @date 2023-05-31\n */\n@ChannelHandler.Sharable\npublic class ClientMessageHandler extends SimpleChannelInboundHandler<BarMessage> {\n\n    final ClientUserChannel clientUserChannel;\n\n    public ClientMessageHandler( ClientUser clientUser) {\n        this.clientUserChannel = clientUser.getClientUserChannel();\n    }\n\n    @Override\n    protected void channelRead0(ChannelHandlerContext ctx, BarMessage message) {\n        clientUserChannel.readMessage(message);\n    }\n}"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/kit/AssertKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.kit;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgException;\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-05\n */\n@UtilityClass\npublic class AssertKit {\n    public void assertTrue(boolean value) {\n        assertTrue(value, \"error\");\n    }\n\n    public void assertTrue(boolean value, String errorMsg) {\n        if (!value) {\n            throw new MsgException(0, errorMsg);\n        }\n    }\n\n    public void assertTrueThrow(boolean value) {\n        assertTrueThrow(value, \"error\");\n    }\n\n    public void assertTrueThrow(boolean value, String errorMsg) {\n        if (value) {\n            throw new MsgException(0, errorMsg);\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/kit/ClientKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.kit;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport lombok.experimental.UtilityClass;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-15\n */\n@UtilityClass\npublic class ClientKit {\n    public String toInputName(CmdInfo cmdInfo) {\n        return cmdInfo.getCmd() + \"-\" + cmdInfo.getSubCmd();\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/kit/ClientUserConfigs.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.kit;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * 模拟客户端相关的配置\n *\n * @author 渔民小镇\n * @date 2023-07-15\n */\n@UtilityClass\npublic class ClientUserConfigs {\n    /**\n     * 关闭控制台输入配置\n     * <pre>\n     *     在压测时建议关闭，也就是将该属性设置为 true。\n     *\n     *     当开启关闭控制台输入配置时，也就是将该属性设置为 true 时，控制台输入相关的将失效。\n     * </pre>\n     */\n    public boolean closeScanner;\n\n    /** true 开启广播监听触发日志 */\n    public boolean openLogListenBroadcast = true;\n\n    /** true 开启客户端向服务器发送请求的日志 */\n    public boolean openLogRequestCommand = true;\n\n    /** true 开启请求回调的日志 */\n    public boolean openLogRequestCallback = true;\n\n    /**\n     * true 表示不能存在相同的模拟命令\n     * <pre>\n     *     默认为 false，不做任何检测\n     * </pre>\n     */\n    public boolean uniqueInputCommand;\n\n    /** true 开启心跳回调的日志 */\n    public boolean openLogIdle = false;\n\n    /**\n     * 关闭模拟请求相关日志\n     */\n    public void closeLog() {\n        openLogListenBroadcast = false;\n        openLogRequestCommand = false;\n        openLogRequestCallback = false;\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/kit/ScannerKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.kit;\n\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Scanner;\n\n/**\n * 模拟客户端工具\n *\n * @author 渔民小镇\n * @date 2023-07-04\n */\n@UtilityClass\npublic class ScannerKit {\n\n    final Scanner scanner = new Scanner(System.in);\n\n    /**\n     * 当开启关闭控制台输入配置时，将不执行 runnable\n     *\n     * @param runnable runnable\n     */\n    public void log(Runnable runnable) {\n        if (ClientUserConfigs.closeScanner) {\n            return;\n        }\n\n        runnable.run();\n    }\n\n    /**\n     * 控制台输入 String 值\n     * <pre>\n     *     如果开启了关闭了控制台输入的配置，将使用 value 做为默认值返回\n     * </pre>\n     *\n     * @param defaultValue 当开启了关闭了控制台输入，value 将作为默认值返回\n     * @return String value\n     */\n    public String nextLine(String defaultValue) {\n        if (ClientUserConfigs.closeScanner) {\n            return defaultValue;\n        }\n\n        return nextLine();\n    }\n\n    /**\n     * 控制台输入 String 值\n     *\n     * @return String value\n     */\n    public String nextLine() {\n        if (ClientUserConfigs.closeScanner) {\n            // 不支持控制台输入\n            ThrowKit.ofRuntimeException(\"No support for console input\");\n        }\n\n        return scanner.nextLine();\n    }\n\n    /**\n     * 控制台输入 long 值\n     * <pre>\n     *     如果开启了关闭了控制台输入的配置，将使用 value 做为默认值返回\n     * </pre>\n     *\n     * @param defaultValue 当开启了关闭了控制台输入，value 将作为默认值返回\n     * @return long value\n     */\n    public long nextLong(long defaultValue) {\n        if (ClientUserConfigs.closeScanner) {\n            return defaultValue;\n        }\n\n        return nextLong();\n    }\n\n    /**\n     * 控制台输入 long 值\n     *\n     * @return long value\n     */\n    public long nextLong() {\n        if (ClientUserConfigs.closeScanner) {\n            // 不支持控制台输入\n            ThrowKit.ofRuntimeException(\"No support for console input\");\n        }\n\n        String s = ScannerKit.scanner.nextLine();\n        return Long.parseLong(s);\n    }\n\n    /**\n     * 控制台输入 int 值\n     * <pre>\n     *     如果开启了关闭了控制台输入的配置，将使用 value 做为默认值返回\n     * </pre>\n     *\n     * @param defaultValue 当开启了关闭了控制台输入，value 将作为默认值返回\n     * @return int value\n     */\n    public int nextInt(int defaultValue) {\n        if (ClientUserConfigs.closeScanner) {\n            return defaultValue;\n        }\n\n        return nextInt();\n    }\n\n    /**\n     * 控制台输入 int 值\n     *\n     * @return int value\n     */\n    public int nextInt() {\n        if (ClientUserConfigs.closeScanner) {\n            // 不支持控制台输入\n            ThrowKit.ofRuntimeException(\"No support for console input\");\n        }\n\n        String s = ScannerKit.scanner.nextLine();\n        return Integer.parseInt(s);\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/kit/SplitParam.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.kit;\n\nimport com.iohao.game.common.kit.SafeKit;\nimport com.iohao.game.common.kit.StrKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * @author 渔民小镇\n * @date 2023-08-06\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class SplitParam {\n    String[] split;\n\n    public SplitParam(String text) {\n        this(text, \"-\");\n    }\n\n    public SplitParam(String text, String regex) {\n        if (StrKit.isEmpty(text)) {\n            text = \"\";\n        }\n\n        this.split = text.split(regex);\n    }\n\n    public int getInt(int index, int defaultValue) {\n        if (index >= split.length) {\n            return defaultValue;\n        }\n\n        return SafeKit.getInt(split[index], defaultValue);\n    }\n\n    public String getString(int index, String defaultValue) {\n        if (index >= split.length) {\n            return defaultValue;\n        }\n\n        return split[index];\n    }\n\n    public String getString(int index) {\n        if (index >= split.length) {\n            return null;\n        }\n\n        return split[index];\n    }\n\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - <a href=\"https://iohao.github.io/game/docs/extension_module/simulation_client\">压测、模拟客户端请求</a>\n *\n * @author 渔民小镇\n * @date 2024-08-08\n */\npackage com.iohao.game.external.client;"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientChannelRead.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.user;\n\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-08\n */\npublic interface ClientChannelRead {\n    void read(BarMessage message);\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.user;\n\nimport com.iohao.game.common.kit.attr.AttrOptionDynamic;\nimport com.iohao.game.external.client.InputCommandRegion;\n\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2023-07-15\n */\npublic interface ClientUser extends AttrOptionDynamic {\n\n    ClientUserChannel getClientUserChannel();\n\n    ClientUserInputCommands getClientUserInputCommands();\n\n    void setInputCommandRegions(List<InputCommandRegion> inputCommandRegions);\n\n    long getUserId();\n\n    void setUserId(long userId);\n\n    String getNickname();\n\n    void setNickname(String nickname);\n\n    String getJwt();\n\n    void setJwt(String jwt);\n\n    /**\n     * 是否活跃\n     *\n     * @return true 表示玩家活跃\n     */\n    boolean isActive();\n\n    /**\n     * 登录成功后，将会调用 {@link  InputCommandRegion#loginSuccessCallback()} 方法\n     * <pre>\n     *     需要开发者主动调用触发，一般在登录模拟请求的回调中主动的调用。\n     * </pre>\n     */\n    void callbackInputCommandRegion();\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUserChannel.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.user;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.CmdKit;\nimport com.iohao.game.action.skeleton.core.DataCodecKit;\nimport com.iohao.game.action.skeleton.protocol.BarMessage;\nimport com.iohao.game.action.skeleton.protocol.HeadMetadata;\nimport com.iohao.game.action.skeleton.protocol.RequestMessage;\nimport com.iohao.game.external.client.command.*;\nimport com.iohao.game.external.client.kit.ClientUserConfigs;\nimport com.iohao.game.external.core.message.ExternalMessageCmdCode;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.net.InetSocketAddress;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\n\n/**\n * 玩家通信 channel\n * <pre>\n *     发送请求，接收服务器响应\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-07-13\n */\n@Slf4j\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ClientUserChannel {\n    final AtomicInteger msgIdSeq = new AtomicInteger(1);\n\n    final AtomicBoolean starting = new AtomicBoolean();\n    /**\n     * 请求回调\n     * <pre>\n     *     key : msgId\n     * </pre>\n     */\n    final Map<Integer, RequestCommand> callbackMap = new NonBlockingHashMap<>();\n    /**\n     * 广播监听\n     * <pre>\n     *     key : cmdMerge\n     * </pre>\n     */\n    final Map<Integer, ListenCommand> listenMap = new NonBlockingHashMap<>();\n\n    ClientChannelRead channelRead = new DefaultChannelRead();\n\n    final DefaultClientUser clientUser;\n\n    public Runnable closeChannel;\n    public Consumer<BarMessage> clientChannel;\n    /** 目标 ip （服务器 ip） */\n    public InetSocketAddress inetSocketAddress;\n\n    public ClientUserChannel(DefaultClientUser clientUser) {\n        this.clientUser = clientUser;\n    }\n\n    public void request(InputCommand inputCommand) {\n        CmdInfo cmdInfo = inputCommand.getCmdInfo();\n        // 生成请求参数\n        RequestDataDelegate requestData = inputCommand.getRequestData();\n\n        CallbackDelegate callback = inputCommand.getCallback();\n\n        RequestCommand requestCommand = new RequestCommand()\n                .setCmdMerge(cmdInfo.getCmdMerge())\n                .setTitle(inputCommand.getTitle())\n                .setRequestData(requestData)\n                .setCallback(callback);\n\n        this.execute(requestCommand);\n    }\n\n    public void execute(RequestCommand requestCommand) {\n        int msgId = this.msgIdSeq.incrementAndGet();\n        this.callbackMap.put(msgId, requestCommand);\n        CmdInfo cmdInfo = CmdInfo.of(requestCommand.getCmdMerge());\n\n        RequestMessage requestMessage = BarMessageKit.createRequestMessage(cmdInfo);\n        HeadMetadata headMetadata = requestMessage.getHeadMetadata();\n        headMetadata.setMsgId(msgId);\n\n        RequestDataDelegate requestData = requestCommand.getRequestData();\n        Object data = \"\";\n        if (Objects.nonNull(requestData)) {\n            data = requestData.createRequestData();\n            data = ParseClientRequestDataKit.parse(data);\n\n            byte[] encode = DataCodecKit.encode(data);\n            requestMessage.setData(encode);\n        }\n\n        if (ClientUserConfigs.openLogRequestCommand) {\n            long userId = clientUser.getUserId();\n            log.info(\"User[{}] Request【{}】- [msgId:{}] {} {}\"\n                    , userId\n                    , requestCommand.getTitle()\n                    , msgId\n                    , CmdKit.mergeToShort(cmdInfo.getCmdMerge())\n                    , data\n            );\n        }\n\n        this.writeAndFlush(requestMessage);\n    }\n\n    public void readMessage(BarMessage message) {\n        channelRead.read(message);\n    }\n\n    public void writeAndFlush(BarMessage message) {\n        if (Objects.isNull(this.clientChannel)) {\n            return;\n        }\n\n        InetSocketAddress inetSocketAddress = this.inetSocketAddress;\n        if (Objects.nonNull(inetSocketAddress)) {\n            message.getHeadMetadata().setInetSocketAddress(inetSocketAddress);\n        }\n\n        // 发送数据到游戏服务器\n        this.clientChannel.accept(message);\n    }\n\n    public void addListen(ListenCommand listenCommand) {\n        int cmdMerge = listenCommand.getCmdInfo().getCmdMerge();\n        this.listenMap.put(cmdMerge, listenCommand);\n    }\n\n    public void closeChannel() {\n        this.clientUser.setActive(false);\n        Optional.ofNullable(closeChannel).ifPresent(Runnable::run);\n    }\n\n    class DefaultChannelRead implements ClientChannelRead {\n        @Override\n        public void read(BarMessage message) {\n            HeadMetadata headMetadata = message.getHeadMetadata();\n            int responseStatus = message.getResponseStatus();\n\n            // 表示有异常消息，统一异常处理\n            if (responseStatus != 0) {\n                log.error(\"[ErrorCode:{}] - [ErrorMsg:{}] - {}\", responseStatus, message.getValidatorMsg(), headMetadata.getCmdInfo());\n                return;\n            }\n\n            if (headMetadata.getCmdCode() == ExternalMessageCmdCode.idle) {\n                printLog(message);\n                return;\n            }\n\n            CommandResult commandResult = new CommandResult(message);\n            // 有回调的，交给回调处理\n            int msgId = headMetadata.getMsgId();\n            RequestCommand requestCommand = callbackMap.remove(msgId);\n\n            if (Objects.nonNull(requestCommand)) {\n                printLog(headMetadata, requestCommand);\n\n                Optional.ofNullable(requestCommand.getCallback()).ifPresent(callback -> callback.callback(commandResult));\n\n                return;\n            }\n\n            // 广播监听\n            int cmdMerge = headMetadata.getCmdMerge();\n            ListenCommand listenCommand = listenMap.get(cmdMerge);\n\n            if (Objects.nonNull(listenCommand)) {\n                printLog(listenCommand, cmdMerge);\n                listenCommand.getCallback().callback(commandResult);\n            }\n        }\n\n        private void printLog(BarMessage message) {\n            if (ClientUserConfigs.openLogIdle) {\n                log.info(\"Receive Idle: {}\", message);\n            }\n        }\n\n        private void printLog(ListenCommand listenCommand, int cmdMerge) {\n            if (ClientUserConfigs.openLogListenBroadcast) {\n                log.info(\"Listen Callback [{}] - {}\"\n                        , listenCommand.getTitle()\n                        , CmdKit.mergeToShort(cmdMerge)\n                );\n            }\n        }\n\n        private void printLog(HeadMetadata headMetadata, RequestCommand requestCommand) {\n            if (ClientUserConfigs.openLogRequestCallback) {\n                // 玩家接收服务器的响应数据\n                long userId = clientUser.getUserId();\n                int cmdMerge = headMetadata.getCmdMerge();\n\n                log.info(\"User[{}] Receive【{}】- [msgId:{}] {}\"\n                        , userId\n                        , requestCommand.getTitle()\n                        , headMetadata.getMsgId()\n                        , CmdKit.mergeToShort(cmdMerge)\n                );\n\n                CallbackDelegate callback = requestCommand.getCallback();\n                if (Objects.isNull(callback)) {\n                    log.warn(\"callback is null\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUserInputCommands.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.user;\n\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.toy.IoGameBanner;\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.external.client.command.InputCommand;\nimport com.iohao.game.external.client.command.RequestCommand;\nimport com.iohao.game.external.client.kit.ClientKit;\nimport com.iohao.game.external.client.kit.ClientUserConfigs;\nimport com.iohao.game.external.client.kit.ScannerKit;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * 玩家模拟命令管理器\n * <p>\n * 职责\n * <pre>\n *     添加模拟请求\n *     执行请求\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-07-08\n */\n@Slf4j\npublic class ClientUserInputCommands {\n    final AtomicBoolean starting = new AtomicBoolean();\n    @Getter\n    final ClientUserChannel clientUserChannel;\n\n    Map<String, InputCommand> inputCommandMap = new LinkedHashMap<>();\n\n    public ClientUserInputCommands(ClientUserChannel clientUserChannel) {\n        this.clientUserChannel = clientUserChannel;\n    }\n\n    private void addCommand(InputCommand inputCommand) {\n\n        Objects.requireNonNull(inputCommand);\n        String inputName = inputCommand.getInputName();\n\n        inputCommandMap.put(inputName, inputCommand);\n    }\n\n    public String toInputName(CmdInfo cmdInfo) {\n        return ClientKit.toInputName(cmdInfo);\n    }\n\n    public InputCommand ofCommand(CmdInfo cmdInfo) {\n        InputCommand inputCommand = new InputCommand(cmdInfo);\n        addCommand(inputCommand);\n        return inputCommand;\n    }\n\n    public InputCommand getInputCommand(String inputName) {\n        return inputCommandMap.get(inputName);\n    }\n\n    public InputCommand getInputCommand(CmdInfo cmdInfo) {\n        String inputName = toInputName(cmdInfo);\n        return inputCommandMap.get(inputName);\n    }\n\n    public RequestCommand ofRequestCommand(CmdInfo cmdInfo) {\n        InputCommand inputCommand = this.getInputCommand(cmdInfo);\n\n        return new RequestCommand()\n                .setClientUserChannel(this.clientUserChannel)\n                .setTitle(inputCommand.getTitle())\n                .setCmdMerge(inputCommand.getCmdInfo().getCmdMerge())\n                .setRequestData(inputCommand.getRequestData())\n                .setCallback(inputCommand.getCallback())\n                ;\n    }\n\n    /**\n     * 向服务器发起请求\n     *\n     * @param inputName 请求命令\n     */\n    void request(String inputName) {\n        InputCommand inputCommand = this.getInputCommand(inputName);\n        if (Objects.isNull(inputCommand)) {\n            System.err.printf(\"【%s】命令不存在\\n\", inputName);\n            return;\n        }\n\n        IoGameBanner.println1(inputCommand);\n\n        try {\n            // 发起请求\n            clientUserChannel.request(inputCommand);\n        } catch (Throwable e) {\n            log.error(e.getMessage(), e);\n        }\n    }\n\n    public void help() {\n        IoGameBanner.printlnMsg(\"---------- cmd help ----------\");\n        inputCommandMap.forEach((s, inputCommand) -> IoGameBanner.printlnMsg(inputCommand.toString()));\n        IoGameBanner.printlnMsg(\"------------------------------\");\n    }\n\n    public void listenHelp() {\n        IoGameBanner.printlnMsg(\"---------- 广播监听 help ----------\");\n        clientUserChannel.getListenMap().values().forEach(IoGameBanner::println1);\n        IoGameBanner.printlnMsg(\"------------------------------\");\n    }\n\n    public void start() {\n        if (starting.get()) {\n            return;\n        }\n\n        if (!starting.compareAndSet(false, true)) {\n            return;\n        }\n\n        TaskKit.execute(this::extracted);\n    }\n\n    private void extracted() {\n\n        if (ClientUserConfigs.closeScanner) {\n            // 在压测下，建议关闭\n            return;\n        }\n\n        String input = \"\";\n        String lastInput = \"+\";\n\n        while (!input.equalsIgnoreCase(\"q\")) {\n\n            IoGameBanner.printlnMsg(\"提示：[命令执行 : cmd-subCmd] [退出 : q] [帮助 : help]\");\n\n            try {\n                input = ScannerKit.nextLine();\n                input = input.trim();\n            } catch (Exception e) {\n                log.info(\"在压测下，建议将 ScannerKit.closeScanner 设置为 true，关闭控制台输入！\");\n                log.error(e.getMessage(), e);\n                break;\n            }\n\n            if (StrKit.isEmpty(input)) {\n                continue;\n            }\n\n            if (Objects.equals(input, \"help\") || Objects.equals(input, \".\")) {\n                help();\n                continue;\n            }\n\n            if (Objects.equals(input, \"..\")) {\n                listenHelp();\n                continue;\n            }\n\n            if (Objects.equals(input, \"...\")) {\n                help();\n                listenHelp();\n                continue;\n            }\n\n            if (Objects.equals(input, \"q\")) {\n                IoGameBanner.printlnMsg(\"88，老哥！顺便帮忙关注一下组织 https://github.com/game-town\");\n                System.exit(-1);\n                continue;\n            }\n\n            //  重复上一次命令\n            if (\"+\".equals(input)) {\n                input = lastInput;\n            } else {\n                lastInput = input;\n            }\n\n            // 发起模拟请求\n            request(input);\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUsers.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.user;\n\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * 压力测试时的一个辅助类\n *\n * @author 渔民小镇\n * @date 2023-07-16\n */\n@Slf4j\n@UtilityClass\npublic class ClientUsers {\n    final BlockingQueue<Runnable> runnableQueue = new LinkedBlockingQueue<>();\n    final AtomicBoolean loginSuccess = new AtomicBoolean();\n    final List<ClientUser> clientUsers = new CopyOnWriteArrayList<>();\n\n    static {\n        TaskKit.execute(() -> {\n\n            try {\n                // 小等一会\n                TimeUnit.MILLISECONDS.sleep(100);\n            } catch (InterruptedException e) {\n                log.error(e.getMessage(), e);\n            }\n\n            while (true) {\n                // 等所有玩家登录完成\n                boolean loginSuccess = ClientUsers.isLoginSuccess();\n                if (loginSuccess) {\n                    break;\n                }\n\n                try {\n                    TimeUnit.SECONDS.sleep(1);\n                } catch (InterruptedException e) {\n                    log.error(e.getMessage(), e);\n                }\n            }\n\n            extractedExecute();\n        });\n    }\n\n    boolean executeStart;\n\n    private void extractedExecute() {\n        executeStart = true;\n\n        TaskKit.execute(() -> {\n            if (clientUsers.size() > 1) {\n                int sleep = 5;\n                log.info(\"[{}]个玩家全部登录完成，[{}]秒后开始执行任务[{}]\", clientUsers.size(), sleep, runnableQueue.size());\n\n                try {\n                    TimeUnit.SECONDS.sleep(sleep);\n                } catch (InterruptedException e) {\n                    log.error(e.getMessage(), e);\n                }\n            }\n\n            while (executeStart) {\n                try {\n                    Runnable take = runnableQueue.take();\n                    TaskKit.execute(take);\n                } catch (InterruptedException e) {\n                    log.error(e.getMessage(), e);\n                }\n            }\n        });\n    }\n\n    public void addClientUser(ClientUser clientUser) {\n        clientUsers.add(clientUser);\n    }\n\n    boolean isLoginSuccess() {\n\n        if (loginSuccess.get()) {\n            return true;\n        }\n\n        int size = 0;\n        for (ClientUser clientUser : clientUsers) {\n            if (clientUser.getUserId() != 0) {\n                size++;\n            }\n        }\n\n        loginSuccess.set(clientUsers.size() == size);\n        return loginSuccess.get();\n    }\n\n    /**\n     * 将任务添加到队列中，当玩家全部登录完成后会执行任务\n     *\n     * @param command 任务\n     */\n    public void execute(Runnable command) {\n        runnableQueue.add(command);\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/user/DefaultClientUser.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.user;\n\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport com.iohao.game.external.client.InputCommandRegion;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 客户端的用户（玩家）\n * <pre>\n *     开发者可以通过动态属性来扩展业务，比如可以在动态属性中保存货币、战力值、血条 ...等\n *\n *     也可以通过继承的方式来扩展 ClientUser。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2023-07-09\n */\n@Setter\n@Getter\n@FieldDefaults(level = AccessLevel.PROTECTED)\npublic class DefaultClientUser implements ClientUser {\n    final AttrOptions options = new AttrOptions();\n    /** 通信 channel 用于读写 */\n    final ClientUserChannel clientUserChannel = new ClientUserChannel(this);\n    final ClientUserInputCommands clientUserInputCommands = new ClientUserInputCommands(clientUserChannel);\n    List<InputCommandRegion> inputCommandRegions;\n\n    /** true 已经登录成功 */\n    boolean loginSuccess;\n\n    long userId;\n    /** 昵称 */\n    String nickname;\n    String jwt;\n\n    boolean active = true;\n\n    @Override\n    public void callbackInputCommandRegion() {\n        if (Objects.isNull(this.inputCommandRegions)) {\n            return;\n        }\n\n        this.inputCommandRegions.forEach(inputCommandRegion -> {\n            inputCommandRegion.setClientUser(this);\n            inputCommandRegion.loginSuccessCallback();\n        });\n    }\n}\n"
  },
  {
    "path": "widget/light-client/src/main/java/com/iohao/game/external/client/user/InternalAboutClient.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.external.client.user;\n\nimport com.iohao.game.action.skeleton.core.flow.parser.MethodParsers;\n\nimport java.util.List;\nimport java.util.Objects;\n\nfinal class ParseClientRequestDataKit {\n    static Object parse(Object requestData) {\n        Objects.requireNonNull(requestData);\n\n        boolean isList;\n        Class<?> clazz;\n\n        if (requestData instanceof List<?> list) {\n            Objects.requireNonNull(list.getFirst());\n\n            isList = true;\n            clazz = list.getFirst().getClass();\n        } else {\n            isList = false;\n            clazz = requestData.getClass();\n        }\n\n        var methodParser = MethodParsers.getMethodParser(clazz);\n        return methodParser.parseData(isList, requestData);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>light-domain-event</artifactId>\n    <name>light-domain-event for ioGame</name>\n\n    <dependencies>\n        <!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->\n        <dependency>\n            <groupId>com.lmax</groupId>\n            <artifactId>disruptor</artifactId>\n            <version>${disruptor.version}</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/DisruptorManager.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event;\n\nimport com.iohao.game.widget.light.domain.event.disruptor.EventDisruptor;\nimport com.lmax.disruptor.dsl.Disruptor;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Consumer;\n\n/**\n * 负责 Disruptor 的管理\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic class DisruptorManager {\n    /**\n     * 事件主题\n     * <pre>\n     *     key : topic\n     *     value : 事件订阅\n     * </pre>\n     */\n    private final Map<Class<?>, Disruptor<EventDisruptor>> topicMap = new ConcurrentHashMap<>();\n\n    /** 领域事件线程名前缀 */\n    @Setter\n    @Getter\n    private String threadNamePrefix = \"iohao.domain\";\n\n    /**\n     * 获取所有Disruptor\n     *\n     * @return Disruptor集合\n     */\n    Collection<Disruptor<EventDisruptor>> listDisruptor() {\n        return topicMap.values();\n    }\n\n    void forEach(Consumer<Disruptor<EventDisruptor>> action) {\n        listDisruptor().forEach(action);\n    }\n\n    /**\n     * 获取领域消息主题对应的Disruptor\n     *\n     * @param topic 领域消息主题\n     * @return Disruptor\n     */\n    Disruptor<EventDisruptor> getDisruptor(final Class<?> topic) {\n        return topicMap.get(topic);\n    }\n\n    void put(Class<?> topic, Disruptor<EventDisruptor> disruptor) {\n        topicMap.putIfAbsent(topic, disruptor);\n    }\n\n    private DisruptorManager() {\n    }\n\n    public static DisruptorManager me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final DisruptorManager ME = new DisruptorManager();\n    }\n\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/DomainEventContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event;\n\nimport com.iohao.game.widget.light.domain.event.disruptor.ConsumeEventHandler;\nimport com.iohao.game.widget.light.domain.event.disruptor.DisruptorCreate;\nimport com.iohao.game.widget.light.domain.event.disruptor.EventDisruptor;\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport com.lmax.disruptor.dsl.Disruptor;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\n\n/**\n * 领域事件上下文\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@Slf4j\npublic class DomainEventContext {\n\n    final DomainEventContextParam param;\n\n    public DomainEventContext(DomainEventContextParam param) {\n        this.param = param;\n    }\n\n    /**\n     * <pre>\n     * 初始化配置，Disruptor\n     * 一个topic只能对应一个disruptor，且只有第一个有效，后面添加的将无效。\n     * 如果并发与队列事件同时存在（就是topic是同一个名字），则优先保存并发的topic的。队列的将无效\n     * </pre>\n     *\n     * @return true 成功启动\n     */\n    @SuppressWarnings(\"unchecked\")\n    public boolean startup() {\n        AtomicBoolean init = param.getInit();\n\n        if (!init.compareAndSet(false, true)) {\n            return true;\n        }\n\n        Set<DomainEventHandler<?>> domainEventHandlerSet = param.domainEventHandlerSet;\n        domainEventHandlerSet.stream().collect(Collectors.groupingBy(o -> {\n            // 一个 key 对应多个 value. key 是从领域事件中的接口类型中查找领域实体类型\n            ParameterizedType parameterizedType = (ParameterizedType) o.getClass().getGenericInterfaces()[0];\n            Type type = parameterizedType.getActualTypeArguments()[0];\n            String typeName = type.getTypeName();\n\n            try {\n                return forName(typeName);\n            } catch (ClassNotFoundException e) {\n                throw new RuntimeException(e);\n            }\n        })).forEach((Class<?> topic, List<DomainEventHandler<?>> eventHandlers) -> {\n\n            // 创建 disruptor；并发事件消费 - 无顺序的执行事件消费\n            DisruptorCreate disruptorCreate = param.disruptorCreate;\n            Disruptor<EventDisruptor> disruptor = disruptorCreate.createDisruptor(topic, param);\n            DisruptorManager.me().put(topic, disruptor);\n\n            if (Objects.nonNull(param.exceptionHandler)) {\n                disruptor.setDefaultExceptionHandler(param.exceptionHandler);\n            }\n\n            // disruptor 绑定领域事件消费接口，事件消费绑定\n            eventHandlers.forEach(eventHandler -> {\n                // 事件消费绑定\n                var consumeEventHandler = new ConsumeEventHandler(eventHandler);\n                disruptor.handleEventsWith(consumeEventHandler);\n            });\n        });\n\n        // 启动disruptor\n        DisruptorManager.me().forEach(Disruptor::start);\n\n        domainEventHandlerSet.clear();\n\n        return init.get();\n    }\n\n    private static Class<?> forName(String typeName) throws ClassNotFoundException {\n        try {\n            return Class.forName(typeName);\n        } catch (ClassNotFoundException e) {\n            // 尝试从当前线程的类加载器中加载；#194\n            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();\n            return Class.forName(typeName, true, contextClassLoader);\n        }\n    }\n\n    /**\n     * 停止Disruptor\n     * <pre>\n     *     把环形数组中的事件执行完后停止, 不在接受新的事件\n     * </pre>\n     *\n     * @return true 停止成功\n     */\n    public boolean stop() {\n\n        DisruptorManager.me().listDisruptor().removeIf(disruptor -> {\n            disruptor.shutdown();\n            return true;\n        });\n\n        return true;\n    }\n\n\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/DomainEventContextParam.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event;\n\nimport com.iohao.game.widget.light.domain.event.disruptor.DefaultDisruptorCreate;\nimport com.iohao.game.widget.light.domain.event.disruptor.DisruptorCreate;\nimport com.iohao.game.widget.light.domain.event.exception.DefaultDomainEventExceptionHandler;\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport com.lmax.disruptor.*;\nimport com.lmax.disruptor.dsl.ProducerType;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\n\nimport java.util.HashSet;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@Getter\n@Setter\n@Accessors(chain = true)\npublic class DomainEventContextParam {\n    /** 领域事件消费 */\n    final Set<DomainEventHandler<?>> domainEventHandlerSet = new HashSet<>();\n    /**\n     * 等待策略\n     * <pre>\n     *\n     *     措施                     适用场景                                                                         名称\n     *\n     *     加锁                     CPU资源紧缺，吞吐量和延迟并不重要的场景                                            {@link BlockingWaitStrategy}\n     *     自旋                     通过不断重试，减少切换线程导致的系统调用，而降低延迟。推荐在线程绑定到固定的CPU的场景下使用  {@link BusySpinWaitStrategy}\n     *     自旋 + yield + 自定义策略  CPU资源紧缺，吞吐量和延迟并不重要的场景                                            {@link PhasedBackoffWaitStrategy}\n     *     自旋 + yield + sleep     性能和CPU资源之间有很好的折中。延迟不均匀                                          {@link SleepingWaitStrategy}\n     *     加锁，有超时限制           CPU资源紧缺，吞吐量和延迟并不重要的场景                                            {@link TimeoutBlockingWaitStrategy}\n     *     自旋 + yield + 自旋       性能和CPU资源之间有很好的折中。延迟比较均匀                                        {@link YieldingWaitStrategy}\n     *\n     *\n     * </pre>\n     */\n    WaitStrategy waitStrategy = new LiteBlockingWaitStrategy();\n    ProducerType producerType = ProducerType.MULTI;\n\n    int ringBufferSize = 1024;\n\n    /** 异常处理 */\n    ExceptionHandler<Object> exceptionHandler = new DefaultDomainEventExceptionHandler();\n\n    /** true 初始化完成 */\n    private final AtomicBoolean init = new AtomicBoolean(false);\n    /** 创建 disruptor */\n    DisruptorCreate disruptorCreate = new DefaultDisruptorCreate();\n\n    /**\n     * 添加领域事件消费者，主题默认使用接口实现类的T类型\n     *\n     * @param domainEventHandler 领域事件消费者\n     */\n    public DomainEventContextParam addEventHandler(DomainEventHandler<?> domainEventHandler) {\n        if (Objects.nonNull(domainEventHandler)) {\n            domainEventHandlerSet.add(domainEventHandler);\n        }\n\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/DomainEventPublish.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event;\n\nimport com.iohao.game.widget.light.domain.event.disruptor.DomainEventSource;\nimport com.iohao.game.widget.light.domain.event.disruptor.EventDisruptor;\nimport com.iohao.game.widget.light.domain.event.message.Topic;\nimport com.lmax.disruptor.RingBuffer;\nimport com.lmax.disruptor.dsl.Disruptor;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Objects;\n\n/**\n * 事件发布器\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@UtilityClass\npublic class DomainEventPublish {\n\n    /**\n     * 发送领域事件，这个方法是不会提供返回值的\n     *\n     * @param domainSource 领域消息\n     */\n    public void send(final DomainEventSource domainSource) {\n        publishDomainEvent(domainSource, domainSource.getTopic(), true);\n    }\n\n    /**\n     * 普通对象\n     * <pre>\n     *     发送领域事件\n     * </pre>\n     *\n     * @param domainSource 领域消息\n     */\n    public void send(final Object domainSource) {\n        if (domainSource instanceof DomainEventSource domainEventSource) {\n            send(domainEventSource);\n        } else {\n            // 获取主题\n            Class<?> topic = domainSource instanceof Topic ? ((Topic) domainSource).getTopic() : domainSource.getClass();\n            publishDomainEvent(domainSource, topic, false);\n        }\n    }\n\n    private void publishDomainEvent(final Object domainSource, Class<?> topic, boolean eventSource) {\n\n        // 查找 DomainEvent 对应的 disruptor, 通过事件主题（类信息）获取事件处理 ringBuffers\n        final Disruptor<EventDisruptor> disruptor = DisruptorManager.me().getDisruptor(topic);\n\n        if (Objects.isNull(disruptor)) {\n            throw new NullPointerException(\"没有配置处理 : \" + topic + \" 的领域事件. 请配置\");\n        }\n\n        // 环形数组\n        final RingBuffer<EventDisruptor> ringBuffer = disruptor.getRingBuffer();\n        final long sequence = ringBuffer.next();\n\n        try {\n            /*\n             * 用上面sequence的索引取出一个空的事件用于填充，可以重复使用是因为得益于环形数组\n             * 这样避免了重复创建对象的消耗\n             */\n            final EventDisruptor eventDisruptor = ringBuffer.get(sequence);\n\n            // 设置领域事件\n            if (eventSource) {\n                eventDisruptor.setDomainEventSource((DomainEventSource) domainSource);\n            } else {\n                eventDisruptor.setValue(domainSource);\n            }\n        } finally {\n            //发布事件\n            ringBuffer.publish(sequence);\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/annotation/DomainEvent.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.annotation;\n\n/**\n * 领域事件注解\n * <pre>\n *     用于领域事件注册扫描 (需要自己实现, 这里只是一个标记)\n *     如果没用到扫描的方式, 可以忽略\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic @interface DomainEvent {\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/disruptor/ConsumeEventHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.disruptor;\n\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport com.lmax.disruptor.EventHandler;\n\n/**\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic record ConsumeEventHandler(DomainEventHandler<?> eventHandler) implements EventHandler<EventDisruptor> {\n\n    @Override\n    public void onEvent(EventDisruptor event, long sequence, boolean endOfBatch) {\n        if (event.isEventSource()) {\n            eventHandler.onEvent(event.getDomainEventSource(), sequence, endOfBatch);\n        } else {\n            eventHandler.onEvent(event.getValue(), sequence, endOfBatch);\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/disruptor/DefaultDisruptorCreate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.disruptor;\n\nimport com.iohao.game.widget.light.domain.event.DisruptorManager;\nimport com.iohao.game.widget.light.domain.event.DomainEventContextParam;\nimport com.lmax.disruptor.BatchEventProcessor;\nimport com.lmax.disruptor.WaitStrategy;\nimport com.lmax.disruptor.dsl.Disruptor;\nimport com.lmax.disruptor.dsl.ProducerType;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * 默认的 领域事件构建接口 实现类\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@Slf4j\npublic class DefaultDisruptorCreate implements DisruptorCreate {\n    static final AtomicInteger THREAD_INIT_NUMBER = new AtomicInteger(1);\n\n    @Override\n    public Disruptor<EventDisruptor> createDisruptor(Class<?> topic, DomainEventContextParam param) {\n        int ringBufferSize = param.getRingBufferSize();\n        ProducerType producerType = param.getProducerType();\n        WaitStrategy waitStrategy = param.getWaitStrategy();\n        // 自定义线程工厂\n        ThreadFactory threadFactory = createThreadFactory(topic);\n\n        return new Disruptor<>(EventDisruptor::new, ringBufferSize, threadFactory, producerType, waitStrategy);\n    }\n\n    private ThreadFactory createThreadFactory(Class<?> topic) {\n        return r -> {\n            String domainEventHandlerName = getName(r);\n\n            List<String> nameParamList = new ArrayList<>();\n            // 线程名前缀\n            nameParamList.add(DisruptorManager.me().getThreadNamePrefix());\n            // 主题名\n            nameParamList.add(topic.getSimpleName());\n            // 领域事件名\n            nameParamList.add(domainEventHandlerName);\n            // 编号\n            nameParamList.add(String.valueOf(THREAD_INIT_NUMBER.getAndIncrement()));\n\n            // 组合成线程名\n            String threadName = String.join(\"-\", nameParamList);\n\n            Thread thread = new Thread(r);\n            thread.setDaemon(true);\n            thread.setName(threadName);\n\n            return thread;\n        };\n    }\n\n    private String getName(Runnable r) {\n\n        String domainEventHandlerName = \"\";\n\n        if (r instanceof BatchEventProcessor eventProcessor) {\n            try {\n                Field eventHandler = BatchEventProcessor.class.getDeclaredField(\"eventHandler\");\n                eventHandler.setAccessible(true);\n                Object o = eventHandler.get(eventProcessor);\n\n                if (o instanceof ConsumeEventHandler consumeEventHandler) {\n                    domainEventHandlerName = consumeEventHandler.eventHandler().getName();\n                }\n            } catch (NoSuchFieldException | IllegalAccessException e) {\n                log.error(e.getMessage(), e);\n            }\n\n            return domainEventHandlerName;\n        }\n\n        return domainEventHandlerName;\n    }\n}"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/disruptor/DisruptorCreate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.disruptor;\n\nimport com.iohao.game.widget.light.domain.event.DomainEventContextParam;\nimport com.lmax.disruptor.dsl.Disruptor;\n\n/**\n * 领域事件构建接口\n * <pre>\n *     创建disruptor\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic interface DisruptorCreate {\n    /**\n     * 根据topic（领域消息主题）创建disruptor\n     *\n     * @param topic 主题\n     * @param param param\n     * @return Disruptor\n     */\n    Disruptor<EventDisruptor> createDisruptor(Class<?> topic, DomainEventContextParam param);\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/disruptor/DomainEventSource.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.disruptor;\n\nimport com.iohao.game.widget.light.domain.event.message.Topic;\n\n/**\n * 领域事件接口 - 源事件源\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic interface DomainEventSource extends Topic {\n    /**\n     * 获取领域事件主题\n     *\n     * @return 领域事件主题\n     */\n    @Override\n    default Class<?> getTopic() {\n        return this.getClass();\n    }\n\n    /**\n     * 获取事件源\n     *\n     * @param <T> source\n     * @return 事件源\n     */\n    @SuppressWarnings(\"unchecked\")\n    default <T> T getSource() {\n        return (T) this;\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/disruptor/EventDisruptor.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.disruptor;\n\nimport com.lmax.disruptor.RingBuffer;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 事件订阅发送 {@link EventDisruptor} 给 {@link RingBuffer}\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class EventDisruptor {\n\n    /**\n     * 没有实现 {@link DomainEventSource} 接口的对象\n     */\n    Object value;\n\n    /**\n     * 是否包装的领域事件\n     * <pre>\n     *     true 实现了 {@link DomainEventSource} 接口的对象\n     *     false 没有实现 {@link DomainEventSource} 接口的对象\n     * </pre>\n     */\n    @Setter\n    @Getter\n    boolean eventSource = true;\n\n    /**\n     * 领域事件\n     */\n    DomainEventSource domainEventSource;\n\n    /**\n     * 获取领域事件源对象\n     *\n     * @param <T> source\n     * @return 事件源对象\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getDomainEventSource() {\n        return (T) domainEventSource;\n    }\n\n    /**\n     * 设置领域事件源\n     *\n     * @param domainEventSource 领域事件源\n     */\n    public void setDomainEventSource(DomainEventSource domainEventSource) {\n        this.eventSource = true;\n        this.domainEventSource = domainEventSource;\n    }\n\n    /**\n     * 没有实现 {@link DomainEventSource} 接口的对象\n     *\n     * @param <T> T\n     * @return value\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getValue() {\n        return (T) value;\n    }\n\n    /**\n     * 没有实现 {@link DomainEventSource} 接口的对象\n     *\n     * @param value value\n     */\n    public void setValue(Object value) {\n        this.eventSource = false;\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/disruptor/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - domain-event 领域事件 - 领域事件构建接口、源事件源相关\n *\n * @author 渔民小镇\n * @date 2024-08-08\n */\npackage com.iohao.game.widget.light.domain.event.disruptor;"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/exception/DefaultDomainEventExceptionHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.exception;\n\nimport com.lmax.disruptor.ExceptionHandler;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 默认领域事件 异常处理类\n *\n * @author 渔民小镇\n * @date 2022-01-14\n */\n@Slf4j\npublic class DefaultDomainEventExceptionHandler implements ExceptionHandler<Object> {\n    @Override\n    public void handleEventException(Throwable ex, long sequence, Object event) {\n        log.error(ex.getMessage(), ex);\n    }\n\n    @Override\n    public void handleOnStartException(Throwable ex) {\n        log.error(ex.getMessage(), ex);\n    }\n\n    @Override\n    public void handleOnShutdownException(Throwable ex) {\n        log.error(ex.getMessage(), ex);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/exception/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - domain-event 领域事件 - 异常处理相关\n *\n * @author 渔民小镇\n * @date 2024-08-08\n */\npackage com.iohao.game.widget.light.domain.event.exception;"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/message/DomainEventHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.message;\n\n/**\n * 领域事件消费接口, 接收一个领域事件\n *\n * @param <T> T 领域实体\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@FunctionalInterface\npublic interface DomainEventHandler<T> {\n\n    /**\n     * 事件处理\n     *\n     * @param event      领域实体\n     * @param endOfBatch endOfBatch\n     */\n    void onEvent(final T event, final boolean endOfBatch);\n\n    /**\n     * 事件处理\n     *\n     * @param event      领域实体\n     * @param sequence   sequence\n     * @param endOfBatch endOfBatch\n     */\n    default void onEvent(final T event, final long sequence, final boolean endOfBatch) {\n        this.onEvent(event, endOfBatch);\n    }\n\n    /**\n     * 获取领域事件名\n     *\n     * @return 领域事件名\n     */\n    default String getName() {\n        return this.getClass().getSimpleName();\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/message/Eo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.message;\n\nimport com.iohao.game.widget.light.domain.event.DomainEventPublish;\nimport com.iohao.game.widget.light.domain.event.disruptor.DomainEventSource;\n\n/**\n * 领域事件的业务接口 (Event Object)\n * <pre>\n *     通常是业务数据载体实现的接口\n// *     实现该接口后，会得到领域事件发送的能力\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic interface Eo extends DomainEventSource {\n    /**\n     * 领域事件发送\n     */\n    default void send() {\n        DomainEventPublish.send(this);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/message/Topic.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.message;\n\n/**\n * 主题\n * <pre>\n *     领域消息主题\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic interface Topic {\n    /**\n     * 获取主题\n     *\n     * @return 主题\n     */\n    Class<?> getTopic();\n}"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/message/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - domain-event 领域事件 - 自定义领域消息实体与、自定义领域事件消费者的相关消息\n * <pre>\n *     开发者只需要关注两个接口\n *     1. {@link com.iohao.game.widget.light.domain.event.message.Eo} 自定义领域消息实体实现该接口后，会得到领域事件发送的能力\n *     2. {@link com.iohao.game.widget.light.domain.event.message.DomainEventHandler} 定义领域事件消费者，接收一个领域事件\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npackage com.iohao.game.widget.light.domain.event.message;"
  },
  {
    "path": "widget/light-domain-event/src/main/java/com/iohao/game/widget/light/domain/event/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - <a href=\"https://iohao.github.io/game/docs/extension_module/domain_event\">domain-event 领域事件</a> - 可为你的系统实现类似 Guava-EventBus、Spring 事件驱动模型 ApplicationEvent、业务解耦、规避并发、不阻塞主线程...等，各种浪操作。\n * <p>\n * 介绍\n * <pre>\n *     Disruptor 是一个开源的并发框架。由英国外汇交易公司LMAX开发的一个高性能队列，并且大大的简化了并发程序开发的难度，获得2011Duke’s程序框架创新奖。可以将 disruptor 理解为单机版本的 MQ（轻量级单机最快MQ -- disruptor）。\n * </pre>\n * Event Source 领域事件优点\n * <pre>\n * 1. 领域驱动设计，基于LMAX架构。\n * 2. 单一职责原则，可以给系统的可扩展、高伸缩、低耦合达到极致。\n * 3. 异步高并发、线程安全的，使用 disruptor 环形数组来消费业务。\n * 4. 使用事件消费的方式编写代码，即使业务在复杂也不会使得代码混乱，维护代码成本更低。\n * 5. 可灵活的定制业务线程模型\n * 6. 插件形式提供事件领域，做到了可插拔，就像玩乐高积木般有趣。\n * </pre>\n * for example\n * <pre>{@code\n * // 自定义领域消息实体\n * public record StudentEo(int id) implements Eo {\n * }\n *\n * // 定义领域事件 - 用于处理 StudentEo 领域消息实体，一个事件消费类只处理一件事件（单一职责原则）\n * public final class StudentEmailEventHandler1 implements DomainEventHandler<StudentEo> {\n *     @Override\n *     public void onEvent(StudentEo studentEo, boolean endOfBatch) {\n *         log.debug(\"给这个学生发送一个email消息: {}\", studentEo);\n *     }\n * }\n *\n * // 测试用例\n * public class StudentDomainEventTest {\n *     ... ...省略部分代码\n *\n *     @Before\n *     public void setUp() {\n *         // ======项目启动时配置一次（初始化）======\n *\n *         // 领域事件上下文参数\n *         DomainEventContextParam contextParam = new DomainEventContextParam();\n *         // 配置一个学生的领域事件消费 - 给学生发生一封邮件\n *         contextParam.addEventHandler(new StudentEmailEventHandler1());\n *         // 配置一个学生的领域事件消费 - 回家\n *         contextParam.addEventHandler(new StudentGoHomeEventHandler2());\n *         // 配置一个学生的领域事件消费 - 让学生睡觉\n *         contextParam.addEventHandler(new StudentSleepEventHandler3());\n *\n *         // 启动事件驱动\n *         domainEventContext = new DomainEventContext(contextParam);\n *         domainEventContext.startup();\n *     }\n *\n *     @Test\n *     public void testEventSend() {\n *         // 这里开始就是你的业务代码\n *         StudentEo studentEo = new StudentEo(1);\n *         // 发送事件、上面只配置了一个事件。\n *         // 如果将来还需要给学生发送一封email,那么直接配置。（可扩展）\n *         // 如果将来还需要记录学生今天上了什么课程，那么也是直接配置 （可扩展） 这里的业务代码无需任何改动（松耦合）\n *         // 如果将来又不需要给学生发送email的事件了，直接删除配置即可，这里还是无需改动代码。（高伸缩）\n *         studentEo.send();\n *     }\n * }\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npackage com.iohao.game.widget.light.domain.event;"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/StudentDomainEventTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event;\n\nimport com.iohao.game.widget.light.domain.event.student.*;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/**\n * <pre>\n *     领域事件的启动与停止,整个系统的生命周期只需要做一次.\n *     例如在你的应用中启动, 那么启动领取事件 DomainEventHandlerConfig.start();\n *     应用关闭时可以调用:DomainEventHandlerConfig.stop()\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic class StudentDomainEventTest {\n\n    DomainEventContext domainEventContext;\n\n    @After\n    public void tearDown() throws Exception {\n        // 事件消费完后 - 事件停止\n        domainEventContext.stop();\n    }\n\n    @Before\n    public void setUp() {\n        // ======项目启动时配置一次（初始化）======\n\n        // 领域事件上下文参数\n        DomainEventContextParam contextParam = new DomainEventContextParam();\n        // 配置一个学生的领域事件消费 - 给学生发生一封邮件\n        contextParam.addEventHandler(new StudentEmailEventHandler1());\n        // 配置一个学生的领域事件消费 - 回家\n        contextParam.addEventHandler(new StudentGoHomeEventHandler2());\n        // 配置一个学生的领域事件消费 - 让学生睡觉\n        contextParam.addEventHandler(new StudentSleepEventHandler3());\n\n        // 启动事件驱动\n        domainEventContext = new DomainEventContext(contextParam);\n        domainEventContext.startup();\n    }\n\n    @Test\n    public void testEventSend() {\n        // 这里开始就是你的业务代码\n        StudentEo studentEo = new StudentEo(1);\n        /*\n         * 发送事件、上面只配置了一个事件。\n         * 如果将来还需要给学生发送一封email,那么直接配置。（可扩展）\n         * 如果将来还需要记录学生今天上了什么课程，那么也是直接配置 （可扩展） 这里的业务代码无需任何改动（松耦合）\n         * 如果将来又不需要给学生发送email的事件了，直接删除配置即可，这里还是无需改动代码。（高伸缩）\n         */\n        studentEo.send();\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/StudentDomainEventTest2.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event;\n\nimport com.iohao.game.widget.light.domain.event.student.StudentCountEventHandler;\nimport com.iohao.game.widget.light.domain.event.student.StudentEo;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author 渔民小镇\n * @date 2022-09-02\n */\n@Slf4j\npublic class StudentDomainEventTest2 {\n\n\n    DomainEventContext domainEventContext;\n\n    @After\n    public void tearDown() throws Exception {\n        // 事件消费完后 - 事件停止\n        domainEventContext.stop();\n    }\n\n    @Before\n    public void setUp() {\n        // ======项目启动时配置一次（初始化）======\n\n        // 领域事件上下文参数\n        DomainEventContextParam contextParam = new DomainEventContextParam();\n\n        contextParam.addEventHandler(new StudentCountEventHandler());\n\n        // 启动事件驱动\n        domainEventContext = new DomainEventContext(contextParam);\n        domainEventContext.startup();\n    }\n\n    @Test\n    public void testEventSendSingle() throws InterruptedException {\n        StudentEo studentEo = new StudentEo(1);\n        for (int i = 0; i < 2_000_000; i++) {\n            studentEo.send();\n        }\n\n        log.info(\"start\");\n        TimeUnit.SECONDS.sleep(2);\n        log.info(\"StudentCountEventHandler.longAdder : {}\", StudentCountEventHandler.longAdder);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/UserLoginDomainEventTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event;\n\nimport com.iohao.game.widget.light.domain.event.user.UserLogin;\nimport com.iohao.game.widget.light.domain.event.user.UserLoginEmailEventHandler;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic class UserLoginDomainEventTest {\n\n    DomainEventContext domainEventContext;\n\n    @After\n    public void tearDown() throws Exception {\n        // 事件消费完后 - 事件停止\n        domainEventContext.stop();\n    }\n\n    @Before\n    public void setUp() {\n        // ======项目启动时配置一次（初始化）======\n\n        // 领域事件上下文参数\n        DomainEventContextParam contextParam = new DomainEventContextParam();\n        // 用户登录就发送 email\n        contextParam.addEventHandler(new UserLoginEmailEventHandler());\n\n        // 启动事件驱动\n        domainEventContext = new DomainEventContext(contextParam);\n        domainEventContext.startup();\n    }\n\n    @Test\n    public void testEventSend() {\n        // 这里开始就是你的业务代码\n        UserLogin userLogin = new UserLogin(101, \"塔姆\");\n        /*\n         * 发送事件、上面只配置了一个事件。\n         * 如果将来还需要给用户登录 记录登录日志,那么直接配置。（可扩展）\n         * 如果将来还需要记录用户登录 今天上了什么课程，那么也是直接配置 （可扩展） 这里的业务代码无需任何改动（松耦合）\n         * 如果将来又不需要给用户登录发送email的事件了，直接删除配置即可，这里还是无需改动代码。（高伸缩）\n         */\n        DomainEventPublish.send(userLogin);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/student/StudentCountEventHandler.java",
    "content": "/*\n * ioGame \n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.student;\n\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author 渔民小镇\n * @date 2022-09-02\n */\n@Slf4j\npublic class StudentCountEventHandler implements DomainEventHandler<StudentEo> {\n    public static long longAdder = 0;\n\n    @Override\n    public void onEvent(StudentEo event, boolean endOfBatch) {\n        longAdder++;\n        if (longAdder > 1_999_998) {\n            log.info(\"longAdder : {}\", longAdder);\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/student/StudentEmailEventHandler1.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.student;\n\nimport com.iohao.game.widget.light.domain.event.annotation.DomainEvent;\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 给学生发送email事件\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@Slf4j\n@DomainEvent\npublic final class StudentEmailEventHandler1 implements DomainEventHandler<StudentEo> {\n    @Override\n    public void onEvent(StudentEo studentEo, boolean endOfBatch) {\n        log.debug(\"给这个学生发送一个email消息: {}\", studentEo);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/student/StudentEo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.student;\n\nimport com.iohao.game.widget.light.domain.event.message.Eo;\n\n/**\n * 领域消息 - 学生\n * <pre>\n *     推荐定义领域事件实体类的时候都使用final\n *     避免某个领域事件对该实体进行数据修改\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic record StudentEo(int id) implements Eo {\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/student/StudentGoHomeEventHandler2.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.student;\n\nimport com.iohao.game.widget.light.domain.event.annotation.DomainEvent;\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 学生回家事件\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@Slf4j\n@DomainEvent\npublic final class StudentGoHomeEventHandler2 implements DomainEventHandler<StudentEo> {\n    @Override\n    public void onEvent(StudentEo studentEo, boolean endOfBatch) {\n        log.debug(\"学生回家: {} , {}\", studentEo, endOfBatch);\n    }\n}\n\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/student/StudentSleepEventHandler3.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.student;\n\nimport lombok.extern.slf4j.Slf4j;\nimport com.iohao.game.widget.light.domain.event.annotation.DomainEvent;\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\n\n/**\n * 学生睡觉\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@Slf4j\n@DomainEvent\npublic final class StudentSleepEventHandler3 implements DomainEventHandler<StudentEo> {\n    @Override\n    public void onEvent(StudentEo studentEo, boolean endOfBatch) {\n        log.debug(\"学生睡觉: {}\", studentEo);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/student/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 自定义示例\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npackage com.iohao.game.widget.light.domain.event.student;"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/user/UserLogin.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.user;\n\n/**\n * 用户登录\n * <pre>\n *     这个实体是模拟第三方库的实体\n *     实际中我们是不能修改的\n * </pre>\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npublic record UserLogin(int id, String name) {\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/user/UserLoginEmailEventHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.domain.event.user;\n\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 用户登录就发送 email\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\n@Slf4j\npublic class UserLoginEmailEventHandler implements DomainEventHandler<UserLogin> {\n    @Override\n    public void onEvent(UserLogin userLogin, boolean endOfBatch) {\n        log.info(\"给登录用户发送 email ! {}\", userLogin);\n    }\n}\n"
  },
  {
    "path": "widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/user/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 为第三方库 增加领域事件能力\n *\n * @author 渔民小镇\n * @date 2021-12-26\n */\npackage com.iohao.game.widget.light.domain.event.user;"
  },
  {
    "path": "widget/light-game-room/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>light-game-room</artifactId>\n    <name>light-game-room for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>bolt-client</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>light-domain-event</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/GameRoomService.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport com.iohao.game.widget.light.room.flow.GameFlowService;\nimport com.iohao.game.widget.light.room.operation.OperationService;\n\n/**\n * 游戏房间相关的聚合扩展接口。房间相关的、游戏流程相关的、玩法操作相关的。\n * <pre>\n *     包括内容如下：\n *     1. 房间相关的\n *     2. 开始游戏流程相关的\n *     3. 玩法操作相关的\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @see RoomService 房间相关\n * @see GameFlowService 开始游戏流程相关\n * @see OperationService 玩法操作\n * @since 21.8\n */\npublic interface GameRoomService\n        extends RoomService, GameFlowService, OperationService {\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/Player.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport java.io.Serializable;\n\n/**\n * 玩家接口\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\npublic interface Player extends Serializable {\n    /**\n     * get userId\n     *\n     * @return userId 玩家 id\n     */\n    long getUserId();\n\n    /**\n     * set userId\n     *\n     * @param userId userId 玩家 id\n     */\n    void setUserId(long userId);\n\n    /**\n     * get RoomId\n     *\n     * @return 房间 id\n     */\n    long getRoomId();\n\n    /**\n     * set RoomId\n     *\n     * @param roomId 房间 id\n     */\n    void setRoomId(long roomId);\n\n    /**\n     * get 用户所在位置\n     *\n     * @return 用户所在位置\n     */\n    int getSeat();\n\n    /**\n     * set 用户所在位置\n     *\n     * @param seat 用户所在位置\n     */\n    void setSeat(int seat);\n\n    /**\n     * 是否已经准备\n     *\n     * @return true - 已准备\n     */\n    boolean isReady();\n\n    /**\n     * set 准备状态\n     *\n     * @param ready true - 已准备\n     */\n    void setReady(boolean ready);\n\n    /**\n     * 是否 Robot\n     *\n     * @return true 该玩家是 Robot\n     * @since 21.23\n     */\n    default boolean isRobot() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/Room.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport com.iohao.game.action.skeleton.core.BarMessageKit;\nimport com.iohao.game.action.skeleton.core.CmdInfo;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.action.skeleton.core.flow.FlowContextKit;\nimport com.iohao.game.action.skeleton.protocol.ResponseMessage;\nimport com.iohao.game.common.kit.OperationCode;\nimport com.iohao.game.common.kit.PresentKit;\nimport com.iohao.game.common.kit.concurrent.TaskKit;\nimport com.iohao.game.widget.light.room.flow.RoomCreateContext;\nimport com.iohao.game.widget.light.room.operation.OperationContext;\nimport com.iohao.game.widget.light.room.operation.OperationHandler;\nimport com.iohao.game.widget.light.room.operation.OperationService;\nimport com.iohao.game.widget.light.room.operation.SimpleOperationHandler;\n\nimport java.io.Serializable;\nimport java.util.*;\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\nimport java.util.stream.Stream;\n\n/**\n * 房间接口\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\n@SuppressWarnings(\"unchecked\")\npublic interface Room extends Serializable, RoomBroadcastEnhance {\n    /**\n     * 玩家，包含 Robot\n     * <pre>\n     *     key : userId\n     *     value : player\n     * </pre>\n     *\n     * @return 玩家\n     */\n    Map<Long, Player> getPlayerMap();\n\n    /**\n     * 所有真实的玩家\n     * <pre>\n     *     key : userId\n     *     value : player\n     * </pre>\n     *\n     * @return 真实的玩家\n     * @since 21.23\n     */\n    default Map<Long, Player> getRealPlayerMap() {\n        return Collections.emptyMap();\n    }\n\n    /**\n     * 所有 Robot\n     * <pre>\n     *     key : userId\n     *     value : player\n     * </pre>\n     *\n     * @return Robot Map\n     * @since 21.23\n     */\n    default Map<Long, Player> getRobotMap() {\n        return Collections.emptyMap();\n    }\n\n    /**\n     * 玩家位置\n     * <pre>\n     *     key : seat\n     *     value : userId\n     * </pre>\n     *\n     * @return 玩家位置\n     */\n    Map<Integer, Long> getPlayerSeatMap();\n\n    /**\n     * get roomId\n     *\n     * @return 房间唯一 id\n     */\n    long getRoomId();\n\n    /**\n     * set roomId\n     *\n     * @param roomId 房间唯一 id\n     */\n    void setRoomId(long roomId);\n\n    /**\n     * get 房间空间大小\n     *\n     * @return 房间空间大小。如 4 就是 4 个人上限 (可以根据规则设置)\n     */\n    int getSpaceSize();\n\n    /**\n     * set 房间空间大小\n     *\n     * @param spaceSize 房间空间大小。如 4 就是 4 个人上限 (可以根据规则设置)\n     */\n    void setSpaceSize(int spaceSize);\n\n    /**\n     * get 房间状态\n     *\n     * @return 房间状态\n     * @deprecated Developers should define room states themselves.\n     */\n    @Deprecated\n    RoomStatusEnum getRoomStatusEnum();\n\n    /**\n     * set 房间状态\n     *\n     * @param roomStatusEnum 房间状态\n     * @deprecated Developers should define room states themselves.\n     */\n    @Deprecated\n    void setRoomStatusEnum(RoomStatusEnum roomStatusEnum);\n\n    /**\n     * get 创建房间信息（及玩法规则）\n     *\n     * @return 创建房间信息（及玩法规则）\n     */\n    RoomCreateContext getRoomCreateContext();\n\n    /**\n     * 设置创建房间信息（及玩法规则）\n     *\n     * @param roomCreateContext 创建房间信息（及玩法规则）\n     */\n    void setRoomCreateContext(RoomCreateContext roomCreateContext);\n\n    /**\n     * 房间创建者的 userId\n     *\n     * @return userId\n     */\n    default long getCreatorUserId() {\n        return this.getRoomCreateContext().getCreatorUserId();\n    }\n\n    /**\n     * 当前 userId 是否是房间创建者\n     *\n     * @param userId userId\n     * @return true 是房间创建者\n     */\n    default boolean isCreatorUserId(long userId) {\n        return this.getCreatorUserId() == userId;\n    }\n\n    /**\n     * 玩家列表: 所有玩家，包含 Robot\n     *\n     * @param <T> 玩家\n     * @return 所有玩家\n     */\n    default <T extends Player> Collection<T> listPlayer() {\n        return (Collection<T>) this.getPlayerMap().values();\n    }\n\n    /**\n     * steam players\n     *\n     * @return player Stream\n     */\n    default <T extends Player> Stream<T> streamPlayer() {\n        return (Stream<T>) this.listPlayer().stream();\n    }\n\n    /**\n     * 真实玩家列表: 所有的真实玩家（不包含 Robot）\n     *\n     * @param <T> 玩家\n     * @return 所有玩家\n     */\n    default <T extends Player> Collection<T> listRealPlayer() {\n        return (Collection<T>) this.getRealPlayerMap().values();\n    }\n\n    /**\n     * steam real players （真实的玩家）\n     *\n     * @return player Stream\n     */\n    default <T extends Player> Stream<T> streamRealPlayer() {\n        return (Stream<T>) listRealPlayer().stream();\n    }\n\n    /**\n     * RobotList\n     *\n     * @param <T> Robot\n     * @return RobotList\n     */\n    default <T extends Player> Collection<T> listRobot() {\n        return (Collection<T>) this.getRobotMap().values();\n    }\n\n    /**\n     * steam players\n     *\n     * @return player Stream\n     */\n    default <T extends Player> Stream<T> streamRobot() {\n        return (Stream<T>) this.listRobot().stream();\n    }\n\n    /**\n     * userId Collection\n     *\n     * @return userId\n     */\n    default Collection<Long> listPlayerId() {\n        return this.getPlayerMap().keySet();\n    }\n\n    /**\n     * Robot UserId Collection\n     *\n     * @return userId\n     */\n    default Collection<Long> listRealPlayerId() {\n        return this.getRealPlayerMap().keySet();\n    }\n\n    /**\n     * Robot UserId Collection\n     *\n     * @return userId\n     */\n    default Collection<Long> listRobotPlayerId() {\n        return this.getRobotMap().keySet();\n    }\n\n    /**\n     * 通过 userId 查找玩家\n     *\n     * @param userId userId\n     * @param <T>    Player\n     * @return 玩家\n     */\n    default <T extends Player> T getPlayerById(long userId) {\n        return (T) this.getPlayerMap().get(userId);\n    }\n\n    /**\n     * 玩家是否存在房间内\n     *\n     * @param userId userId\n     * @return true 存在\n     */\n    default boolean existUser(long userId) {\n        return this.getPlayerMap().get(userId) != null;\n    }\n\n    /**\n     * 通过 seat 查找玩家\n     *\n     * @param seat seat\n     * @param <T>  Player\n     * @return 玩家\n     */\n    default <T extends Player> T getPlayerBySeat(int seat) {\n\n        var seatMap = this.getPlayerSeatMap();\n        if (seatMap.containsKey(seat)) {\n            long userId = seatMap.get(seat);\n            return this.getPlayerById(userId);\n        }\n\n        return null;\n    }\n\n    /**\n     * 添加玩家到房间内\n     *\n     * @param player 玩家\n     */\n    default void addPlayer(Player player) {\n        player.setRoomId(this.getRoomId());\n        var userId = player.getUserId();\n        this.getPlayerMap().put(userId, player);\n        this.getPlayerSeatMap().put(player.getSeat(), userId);\n\n        if (notOverrideRealAndRobotMap()) {\n            return;\n        }\n\n        if (player.isRobot()) {\n            this.getRobotMap().put(userId, player);\n        } else {\n            this.getRealPlayerMap().put(userId, player);\n        }\n    }\n\n    /**\n     * 是否重写了 {@link #getRealPlayerMap()} 和 {@link #getRobotMap()} 方法\n     *\n     * @return true 表示其中一个方法没有重写\n     * @since 21.23\n     */\n    private boolean notOverrideRealAndRobotMap() {\n        Object realPlayerMap = this.getRealPlayerMap();\n        Object robotMap = this.getRobotMap();\n        Object emptyMap = Collections.emptyMap();\n        return realPlayerMap == emptyMap || robotMap == emptyMap;\n    }\n\n    /**\n     * 移除玩家\n     *\n     * @param player 玩家\n     */\n    default void removePlayer(Player player) {\n        long userId = player.getUserId();\n        this.getPlayerMap().remove(userId);\n        this.getPlayerSeatMap().remove(player.getSeat());\n\n        if (notOverrideRealAndRobotMap()) {\n            return;\n        }\n\n        if (player.isRobot()) {\n            this.getRobotMap().remove(userId);\n        } else {\n            this.getRealPlayerMap().remove(userId);\n        }\n    }\n\n    /**\n     * 当前房间是否是所指定的房间状态\n     *\n     * @param roomStatusEnum 房间状态\n     * @return true 是所指定的房间状态\n     * @deprecated Developers should define room states themselves.\n     */\n    @Deprecated\n    default boolean isStatus(RoomStatusEnum roomStatusEnum) {\n        return this.getRoomStatusEnum() == roomStatusEnum;\n    }\n\n    /**\n     * 如果玩家在房间内，就执行给定的操作，否则不执行任何操作。\n     *\n     * @param userId userId\n     * @param action 给定操作\n     * @param <T>    t\n     */\n    default <T extends Player> void ifPlayerExist(long userId, Consumer<T> action) {\n        T player = this.getPlayerById(userId);\n        Optional.ofNullable(player).ifPresent(action);\n    }\n\n    /**\n     * 如果玩家不在房间内，就执行给定的操作，否则不执行任何操作。\n     *\n     * @param userId   userId\n     * @param runnable 给定操作\n     */\n    default void ifPlayerNotExist(long userId, Runnable runnable) {\n        var player = this.getPlayerById(userId);\n        PresentKit.ifNull(player, runnable);\n    }\n\n    /**\n     * 统计房间内的玩家数量，包含机器人\n     *\n     * @return 玩家数量\n     */\n    default int countPlayer() {\n        return this.getPlayerMap().size();\n    }\n\n    /**\n     * 统计房间内真实玩家的数量\n     *\n     * @return 真实玩家的数量\n     */\n    default int countRealPlayer() {\n        return this.getRealPlayerMap().size();\n    }\n\n    /**\n     * 统计房间内 Robot 的数量\n     *\n     * @return Robot 数量\n     */\n    default int countRobot() {\n        return this.getRobotMap().size();\n    }\n\n    /**\n     * 房间内是否没有玩家，包含 Robot\n     *\n     * @return true 房间内没有任何玩家\n     */\n    default boolean isEmptyPlayer() {\n        return this.getPlayerMap().isEmpty();\n    }\n\n    /**\n     * 房间内是否没有真实的玩家\n     *\n     * @return true 房间内没有真实玩家\n     */\n    default boolean isEmptyRealPlayer() {\n        return this.getRealPlayerMap().isEmpty();\n    }\n\n    /**\n     * 房间内是否没有 Robot\n     *\n     * @return true 房间内没有 Robot\n     */\n    default boolean isEmptyRobot() {\n        return this.getRealPlayerMap().isEmpty();\n    }\n\n    /**\n     * 是否 Robot\n     *\n     * @param userId userId\n     * @return true: Robot\n     * @since 21.23\n     */\n    default boolean isRobot(long userId) {\n        Player player = this.getPlayerById(userId);\n        return Objects.nonNull(player) && player.isRobot();\n    }\n\n    /**\n     * 是否真实玩家\n     *\n     * @param userId userId\n     * @return true: real player\n     * @since 21.23\n     */\n    default boolean isRealPlayer(long userId) {\n        var player = this.getPlayerById(userId);\n        return Objects.nonNull(player) && !player.isRobot();\n    }\n\n    /**\n     * 得到一个空位置\n     *\n     * @return >=0 表示有位置\n     */\n    default int getEmptySeatNo() {\n        return RoomKit.getEmptySeatNo(this);\n    }\n\n    /**\n     * 是否还有空位\n     *\n     * @return true 还有空的位置\n     * @since 21.23\n     */\n    default boolean hasSeat() {\n        return this.getSpaceSize() > this.countPlayer();\n    }\n\n    /**\n     * 玩家是否都准备了\n     *\n     * @return true 所有玩家都准备了\n     */\n    default boolean isReadyPlayers() {\n        // 是否都准备了。玩家中，如果有任意一个没点准备的，notReady 为 true\n        boolean notReady = this.streamPlayer().anyMatch(player -> !player.isReady());\n        return !notReady;\n    }\n\n    /**\n     * forEach players\n     * <pre>\n     *     the first argument is the userId\n     * </pre>\n     *\n     * @param action action\n     */\n    default void forEach(BiConsumer<Long, Player> action) {\n        this.getPlayerMap().forEach(action);\n    }\n\n\n    /**\n     * Executed in domain events, this method is thread-safe\n     *\n     * @param task task\n     * @since 21.23\n     */\n    default void executeTask(Runnable task) {\n        OperationContext.of(this, SimpleOperationHandler.me()).setCommand(task).send();\n    }\n\n    /**\n     * Delayed execution of tasks, this method is thread-safe\n     *\n     * @param task              task\n     * @param delayMilliseconds delayMilliseconds\n     * @since 21.23\n     */\n    default void executeDelayTask(Runnable task, long delayMilliseconds) {\n        TaskKit.runOnceMillis(() -> this.executeTask(task), delayMilliseconds);\n    }\n\n    /**\n     * setOperationService\n     *\n     * @param operationService operationService\n     * @since 21.28\n     */\n    default void setOperationService(OperationService operationService) {\n    }\n\n    /**\n     * getOperationService\n     *\n     * @return OperationService\n     * @since 21.28\n     */\n    default OperationService getOperationService() {\n        return null;\n    }\n\n    /**\n     * get OperationHandler by OperationCode\n     *\n     * @param operationCode operationCode\n     * @return OperationHandler\n     * @since 21.28\n     */\n    default OperationHandler getOperationHandler(OperationCode operationCode) {\n        return this.getOperationService().getOperationHandler(operationCode);\n    }\n\n    /**\n     * create OperationContext\n     *\n     * @param operationHandler operationHandler\n     * @return OperationContext operationHandler\n     * @since 21.23\n     */\n    default OperationContext ofOperationContext(OperationHandler operationHandler) {\n        return OperationContext.of(this, operationHandler);\n    }\n\n    /**\n     * execute operation\n     *\n     * @param operationCode operationCode\n     * @since 21.28\n     */\n    default void operation(OperationCode operationCode) {\n        var operationHandler = this.getOperationHandler(operationCode);\n        var flowContext = FlowContextKit.ofFlowContext(0);\n        this.operation(operationHandler, flowContext, null);\n    }\n\n    /**\n     * execute operation\n     *\n     * @param operationCode operationCode\n     * @param userId        userId\n     * @since 21.28\n     */\n    default void operation(OperationCode operationCode, long userId) {\n        var operationHandler = this.getOperationHandler(operationCode);\n        var flowContext = FlowContextKit.ofFlowContext(userId);\n        this.operation(operationHandler, flowContext, null);\n    }\n\n    /**\n     * execute operation\n     *\n     * @param operationCode  operationCode\n     * @param userId         userId\n     * @param commandMessage commandMessage\n     * @since 21.28\n     */\n    default void operation(OperationCode operationCode, long userId, Object commandMessage) {\n        var operationHandler = this.getOperationHandler(operationCode);\n        var flowContext = FlowContextKit.ofFlowContext(userId);\n        operation(operationHandler, flowContext, commandMessage);\n    }\n\n    /**\n     * execute operation\n     *\n     * @param operationCode operationCode\n     * @param flowContext   flowContext\n     * @since 21.28\n     */\n    default void operation(OperationCode operationCode, FlowContext flowContext) {\n        OperationHandler operationHandler = this.getOperationHandler(operationCode);\n        this.operation(operationHandler, flowContext, null);\n    }\n\n    /**\n     * execute operation\n     *\n     * @param operationCode  operationCode\n     * @param flowContext    flowContext\n     * @param commandMessage commandMessage\n     * @since 21.28\n     */\n    default void operation(OperationCode operationCode, FlowContext flowContext, Object commandMessage) {\n        OperationHandler operationHandler = this.getOperationHandler(operationCode);\n        this.operation(operationHandler, flowContext, commandMessage);\n    }\n\n    /**\n     * execute operation\n     *\n     * @param operationHandler operationHandler\n     * @param userId           userId\n     * @param commandMessage   commandMessage\n     * @since 21.28\n     */\n    default void operation(OperationHandler operationHandler, long userId, Object commandMessage) {\n        var flowContext = FlowContextKit.ofFlowContext(userId);\n        this.operation(operationHandler, flowContext, commandMessage);\n    }\n\n    /**\n     * execute operation\n     *\n     * @param operationHandler operationHandler\n     * @param flowContext      flowContext\n     * @param commandMessage   commandMessage\n     * @since 21.28\n     */\n    default void operation(OperationHandler operationHandler, FlowContext flowContext, Object commandMessage) {\n        this.ofOperationContext(operationHandler)\n                .setFlowContext(flowContext)\n                .setCommand(commandMessage)\n                .execute();\n    }\n\n    /**\n     * Broadcast data to a specific user.\n     *\n     * @param cmdInfo cmdInfo\n     * @param userId  userId\n     * @param data    data\n     * @since 21.28\n     */\n    default void broadcastToUser(CmdInfo cmdInfo, long userId, Object data) {\n        if (this.isRobot(userId)) {\n            return;\n        }\n\n        this.getAggregationContext().broadcast(cmdInfo, data, userId);\n    }\n\n    /**\n     * Broadcast data to a specific user.\n     *\n     * @param cmdInfo cmdInfo\n     * @param userId  userId\n     * @since 21.28\n     */\n    default void broadcastToUser(CmdInfo cmdInfo, long userId) {\n        if (this.isRobot(userId)) {\n            return;\n        }\n\n        ResponseMessage responseMessage = BarMessageKit.createResponseMessage(cmdInfo);\n        this.getAggregationContext().broadcast(responseMessage, userId);\n    }\n\n    /**\n     * Broadcast data, excluding specified players.\n     *\n     * @param cmdInfo       cmdInfo\n     * @param data          data\n     * @param excludeUserId excludeUserId\n     * @since 21.28\n     */\n    default void broadcastRange(CmdInfo cmdInfo, Object data, long excludeUserId) {\n        if (this.isEmptyRealPlayer()) {\n            return;\n        }\n\n        var playerIdList = this.listRealPlayerId();\n        if (playerIdList.isEmpty()) {\n            return;\n        }\n\n        var rangeBroadcast = this.ofEmptyRangeBroadcast()\n                .addUserId(playerIdList, excludeUserId);\n\n        if (Objects.nonNull(data)) {\n            rangeBroadcast.setResponseMessage(cmdInfo, data);\n        } else {\n            rangeBroadcast.setResponseMessage(cmdInfo);\n        }\n\n        rangeBroadcast.execute();\n    }\n\n    /**\n     * Broadcast data\n     *\n     * @param cmdInfo cmdInfo\n     * @param data    data\n     * @since 21.28\n     */\n    default void broadcastRange(CmdInfo cmdInfo, Object data) {\n        broadcastRange(cmdInfo, data, 0);\n    }\n\n    /**\n     * Broadcast, excluding specified players.\n     *\n     * @param cmdInfo       cmdInfo\n     * @param excludeUserId excludeUserId\n     * @since 21.28\n     */\n    default void broadcastRangeEmpty(CmdInfo cmdInfo, long excludeUserId) {\n        broadcastRange(cmdInfo, null, excludeUserId);\n    }\n\n    /**\n     * Broadcast\n     *\n     * @param cmdInfo cmdInfo\n     * @since 21.28\n     */\n    default void broadcastRangeEmpty(CmdInfo cmdInfo) {\n        broadcastRange(cmdInfo, null, 0);\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomBroadcastEnhance.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;\nimport com.iohao.game.action.skeleton.kit.RangeBroadcaster;\n\nimport java.util.Collection;\n\n/**\n * 房间内的广播增强\n *\n * @author 渔民小镇\n * @date 2024-04-23\n * @since 21.8\n */\ninterface RoomBroadcastEnhance {\n    /**\n     * 房间内的所有玩家 userIds\n     *\n     * @return players，userIds\n     */\n    Collection<Long> listPlayerId();\n\n    /**\n     * 设置通讯上下文\n     * <pre>{@code\n     * // 方式一：通过 flowContext 得到通讯上下文\n     * CommunicationAggregationContext aggregationContext = flowContext.option(FlowAttr.aggregationContext);\n     * // 方式二：通过 BrokerClient 得到通讯上下文\n     * CommunicationAggregationContext aggregationContext = BrokerClientHelper.getBrokerClient().getCommunicationAggregationContext();\n     * }\n     * </pre>\n     *\n     * @param aggregationContext 通讯上下文\n     */\n    void setAggregationContext(CommunicationAggregationContext aggregationContext);\n\n    /**\n     * get 通讯上下文\n     *\n     * @return 通讯上下文\n     */\n    CommunicationAggregationContext getAggregationContext();\n\n    /**\n     * 通过 CommunicationAggregationContext 创建一个 RangeBroadcast，默认会添加上当前房间内的所有玩家\n     *\n     * @param aggregationContext aggregationContext\n     * @return RangeBroadcast 范围内的广播\n     */\n    default RangeBroadcaster ofRangeBroadcast(CommunicationAggregationContext aggregationContext) {\n        return this.ofEmptyRangeBroadcast(aggregationContext)\n                // 添加上房间内的所有玩家\n                .addUserId(this.listPlayerId());\n    }\n\n    /**\n     * 通过 CommunicationAggregationContext 创建一个 RangeBroadcast\n     *\n     * @param aggregationContext aggregationContext\n     * @return RangeBroadcast 范围内的广播\n     */\n    default RangeBroadcaster ofEmptyRangeBroadcast(CommunicationAggregationContext aggregationContext) {\n        return RangeBroadcaster.of(aggregationContext);\n    }\n\n    /**\n     * 创建一个 RangeBroadcast，默认会添加上当前房间内的所有玩家\n     *\n     * @return RangeBroadcast 范围内的广播\n     */\n    default RangeBroadcaster ofRangeBroadcast() {\n        return this.ofRangeBroadcast(this.getAggregationContext());\n    }\n\n    /**\n     * 创建一个 RangeBroadcast\n     *\n     * @return RangeBroadcast 范围内的广播\n     */\n    default RangeBroadcaster ofEmptyRangeBroadcast() {\n        return this.ofEmptyRangeBroadcast(this.getAggregationContext());\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport lombok.experimental.UtilityClass;\n\n/**\n * 房间相关工具类\n *\n * @author 渔民小镇\n * @date 2024-04-30\n * @since 21.7\n */\n@UtilityClass\npublic class RoomKit {\n    /**\n     * 从房间内获取一个空位置\n     *\n     * @param room 房间\n     * @return 空的位置。当值为 -1 时，表示没有空的位置（房间满人了）。\n     */\n    public int getEmptySeatNo(Room room) {\n        // 玩家位置 map\n        var playerSeatMap = room.getPlayerSeatMap();\n\n        for (int i = 0; i < room.getSpaceSize(); i++) {\n            if (!playerSeatMap.containsKey(i)) {\n                return i;\n            }\n        }\n\n        return -1;\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomService.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * 房间管理相关的扩展接口\n * <pre>\n *     房间的添加\n *     房间的删除\n *     房间与玩家之间的关联\n *     房间查找\n *         通过 roomId 查找\n *         通过 userId 查找\n * </pre>\n * 子类扩展实现\n * <pre>\n *     如果你使用了lombok, 推荐这种方式. 只需要在对象中新增此行代码\n *     {@code\n *     // 房间 map\n *     final Map<Long, Room> roomMap = new ConcurrentHashMap<>();\n *     // 玩家对应的房间 map\n *     final Map<Long, Long> userRoomMap = new ConcurrentHashMap<>();\n *     }\n * </pre>\n * 内置的默认实现\n * <pre>{@code\n *     RoomService roomService = RoomService.of();\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\n@SuppressWarnings(\"unchecked\")\npublic interface RoomService {\n    /**\n     * 房间 map\n     * <pre>\n     *     key : roomId\n     *     value : room\n     * </pre>\n     *\n     * @return 房间 map\n     */\n    Map<Long, Room> getRoomMap();\n\n    /**\n     * 玩家对应的房间 map\n     * <pre>\n     *     key : userId\n     *     value : roomId\n     * </pre>\n     *\n     * @return 玩家对应的房间 map\n     */\n    Map<Long, Long> getUserRoomMap();\n\n    /**\n     * 通过 userId 查找房间\n     *\n     * @param userId userId\n     * @param <T>    Room\n     * @return 房间\n     */\n    default <T extends Room> T getRoomByUserId(long userId) {\n        // 通过 userId 得到 roomId\n        Long roomId = this.getUserRoomMap().get(userId);\n\n        if (Objects.isNull(roomId)) {\n            return null;\n        }\n\n        // 通过 roomId 得到 room\n        return getRoom(roomId);\n    }\n\n    /**\n     * 通过 roomId 查找房间\n     *\n     * @param roomId roomId\n     * @param <T>    Room\n     * @return 房间\n     */\n    default <T extends Room> T getRoom(long roomId) {\n        return (T) this.getRoomMap().get(roomId);\n    }\n\n    /**\n     * 通过 roomId 查找房间 Optional\n     *\n     * @param roomId roomId\n     * @param <T>    Room\n     * @return Optional Room\n     */\n    default <T extends Room> Optional<T> optionalRoom(long roomId) {\n        return Optional.ofNullable(this.getRoom(roomId));\n    }\n\n    /**\n     * 通过 userId 查找房间 Optional\n     *\n     * @param userId userId\n     * @param <T>    Room\n     * @return Optional Room\n     */\n    default <T extends Room> Optional<T> optionalRoomByUserId(long userId) {\n        return Optional.ofNullable(this.getRoomByUserId(userId));\n    }\n\n    /**\n     * 添加房间\n     *\n     * @param room 房间\n     */\n    default void addRoom(Room room) {\n        long roomId = room.getRoomId();\n        this.getRoomMap().put(roomId, room);\n    }\n\n    /**\n     * 删除房间\n     *\n     * @param room 房间\n     */\n    default void removeRoom(Room room) {\n        long roomId = room.getRoomId();\n        this.getRoomMap().remove(roomId);\n        room.listPlayerId().forEach(userId -> this.getUserRoomMap().remove(userId));\n    }\n\n    /**\n     * 添加玩家到房间里，并让 userId 与 roomId 关联\n     *\n     * @param room   间里\n     * @param player 玩家\n     */\n    default void addPlayer(Room room, Player player) {\n        room.addPlayer(player);\n        this.getUserRoomMap().put(player.getUserId(), room.getRoomId());\n    }\n\n    /**\n     * 将玩家从房间内内移除 并删除 userId 与 roomId 的关联\n     *\n     * @param room   房间\n     * @param player 玩家\n     */\n    default void removePlayer(Room room, Player player) {\n        room.removePlayer(player);\n        this.getUserRoomMap().remove(player.getUserId());\n    }\n\n    /**\n     * 将玩家从房间内内移除 并删除 userId 与 roomId 的关联\n     *\n     * @param room   房间\n     * @param userId userId\n     */\n    default void removePlayer(Room room, long userId) {\n        room.ifPlayerExist(userId, player -> this.removePlayer(room, player));\n    }\n\n    /**\n     * 得到房间列表\n     *\n     * @param <T> Room\n     * @return 房间\n     */\n    default <T extends Room> Collection<T> listRoom() {\n        return (Collection<T>) this.getRoomMap().values();\n    }\n\n    /**\n     * 创建一个 RoomService 对象实例（框架内置的默认实现）\n     *\n     * @return RoomService\n     */\n    static RoomService of() {\n        return new SimpleRoomService();\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomStatusEnum.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\n/**\n * 房间状态\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 17\n * @deprecated Developers should define room states themselves.\n */\n@Deprecated\npublic enum RoomStatusEnum {\n    /** 等待 */\n    wait,\n    /** 开始 */\n    start,\n    /** 其他（如结算之类的） */\n    none;\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/SimplePlayer.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.io.Serial;\n\n/**\n * 玩家（内置实现）\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PROTECTED)\npublic class SimplePlayer implements Player {\n    @Serial\n    private static final long serialVersionUID = -26338708253909097L;\n    /** userId 玩家 id */\n    long userId;\n    /** 房间 id */\n    long roomId;\n    /** 用户所在位置 */\n    int seat;\n    /** true - 已准备 */\n    boolean ready;\n    /** true robot */\n    boolean robot;\n    /** true 模仿 robot 机制，但并不是真正的 robot，类似于 robot 托管 */\n    boolean maybeRobot;\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/SimpleRoom.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;\nimport com.iohao.game.bolt.broker.core.client.BrokerClientHelper;\nimport com.iohao.game.widget.light.room.flow.RoomCreateContext;\nimport com.iohao.game.widget.light.room.operation.OperationService;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.io.Serial;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.TreeMap;\n\n/**\n * 房间（内置实现）\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class SimpleRoom implements Room {\n    @Serial\n    private static final long serialVersionUID = -6937915481102847959L;\n    /**\n     * 玩家\n     * <pre>\n     *     key is userId\n     *     value is player\n     * </pre>\n     */\n    final Map<Long, Player> playerMap = new NonBlockingHashMap<>();\n    final Map<Long, Player> realPlayerMap = new NonBlockingHashMap<>();\n    final Map<Long, Player> robotMap = new NonBlockingHashMap<>();\n    /**\n     * 玩家位置\n     * <pre>\n     *     key is seat\n     *     value is userId\n     * </pre>\n     */\n    final Map<Integer, Long> playerSeatMap = new TreeMap<>();\n    OperationService operationService;\n    /** 房间唯一 id */\n    long roomId;\n    /** 创建房间信息 */\n    RoomCreateContext roomCreateContext;\n    /** 房间空间大小: 4 就是4个人上限 (根据规则设置) */\n    int spaceSize;\n    /** 房间状态 */\n    @Deprecated\n    RoomStatusEnum roomStatusEnum = RoomStatusEnum.wait;\n    CommunicationAggregationContext aggregationContext;\n\n    public SimpleRoom() {\n        // 为房间设置通讯接口\n        if (Objects.nonNull(BrokerClientHelper.getBrokerClient())) {\n            aggregationContext = BrokerClientHelper.getBrokerClient().getCommunicationAggregationContext();\n        }\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/SimpleRoomService.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\n\n/**\n * 房间管理相关的扩展（内置实现）\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class SimpleRoomService implements RoomService {\n    /**\n     * 房间 map\n     * <pre>\n     *     key : roomId\n     *     value : room\n     * </pre>\n     */\n    final Map<Long, Room> roomMap = new NonBlockingHashMap<>();\n\n    /**\n     * 玩家对应的房间 map\n     * <pre>\n     *     key : userId\n     *     value : roomId\n     * </pre>\n     */\n    final Map<Long, Long> userRoomMap = new NonBlockingHashMap<>();\n\n    SimpleRoomService() {\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/domain/GameFlowEo.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.domain;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.widget.light.domain.event.message.Eo;\n\n/**\n * GameFlowEo，可规避 GameFlowService 中的并发问题\n * <pre>{@code\n * // 发送领域事件\n * GameFlowContext context = GameFlowContext.of(room, flowContext);\n * new GameFlowEo(flowContext, () -> {\n *     // 进入房间\n *     this.roomService.enterRoom(context);\n * }).send();\n *\n * GameFlowContext gameFlowContext = GameFlowContext.of(room, flowContext);\n * new GameFlowEo(flowContext, () -> {\n *     // 退出房间\n *     this.roomService.quitRoom(gameFlowContext);\n * }).send();\n *\n * GameFlowContext context = GameFlowContext.of(room, flowContext);\n * new GameFlowEo(flowContext, () -> {\n *     // 开始游戏\n *     this.roomService.startGame(context);\n * }).send();\n *\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @see GameFlowEventHandler\n * @since 21.8\n */\npublic record GameFlowEo(FlowContext flowContext, Runnable runnable) implements Eo {\n    public void execute() {\n        runnable.run();\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/domain/GameFlowEventHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.domain;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionKit;\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport com.iohao.game.widget.light.room.flow.GameFlowService;\n\n/**\n * EventHandler，消费 GameFlowEo\n * <p>\n * 将 GameFlowEventHandler 添加到 DomainEventContext 中\n * <pre>{@code\n *         // 领域事件上下文参数\n *         DomainEventContextParam contextParam = new DomainEventContextParam();\n *         // 游戏流程相关\n *         contextParam.addEventHandler(new GameFlowEventHandler());\n *\n *         // 启动事件驱动\n *         DomainEventContext domainEventContext = new DomainEventContext(contextParam);\n *         domainEventContext.startup();\n * }\n *\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @see GameFlowService\n * @see GameFlowEo\n * @since 21.8\n */\npublic final class GameFlowEventHandler implements DomainEventHandler<GameFlowEo> {\n    @Override\n    public void onEvent(GameFlowEo event, boolean endOfBatch) {\n        try {\n            event.execute();\n        } catch (Throwable e) {\n            MsgExceptionKit.onException(e, event.flowContext());\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/domain/OperationContextEventHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.domain;\n\nimport com.iohao.game.action.skeleton.core.exception.MsgExceptionKit;\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.widget.light.domain.event.message.DomainEventHandler;\nimport com.iohao.game.widget.light.room.operation.OperationContext;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 玩法操作上下文领域事件，用于规避并发\n * <pre>{@code\n * // 创建玩法操作上下文\n * OperationContext operationContext = OperationContext.of(room, operationHandler)\n *     // 当前操作的玩家\n *     .setFlowContext(flowContext)\n *     // 开发者根据游戏业务定制的操作数据\n *     .setCommand(command);\n *\n * // 领域事件相关，https://iohao.github.io/game/docs/extension_module/domain_event\n * DomainEventPublish.send(operationContext);\n * }\n * </pre>\n * <p>\n * 将 OperationContextEventHandler 添加到 DomainEventContext 中\n * <pre>{@code\n *         // 领域事件上下文参数\n *         DomainEventContextParam contextParam = new DomainEventContextParam();\n *         // 配置领域事件 - 玩法操作相关\n *         contextParam.addEventHandler(new OperationContextEventHandler());\n *\n *         // 启动事件驱动\n *         DomainEventContext domainEventContext = new DomainEventContext(contextParam);\n *         domainEventContext.startup();\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\n@Slf4j\npublic final class OperationContextEventHandler implements DomainEventHandler<OperationContext> {\n    @Override\n    public void onEvent(OperationContext operationContext, boolean endOfBatch) {\n        try {\n            // 玩法操作业务类，将验证与操作分离\n            operationContext.execute();\n        } catch (Throwable e) {\n            FlowContext flowContext = operationContext.getFlowContext();\n            if (flowContext == null) {\n                log.error(e.getMessage(), e);\n                return;\n            }\n\n            // 将异常发送给当前用户\n            MsgExceptionKit.onException(e, flowContext);\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/domain/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - 桌游类、房间类游戏 - 规避并发的领域事件。\n * see <a href=\"https://iohao.github.io/game/docs/extension_module/domain_event\">文档 - domain-event 领域事件</a>\n * 在使用 room 模块的领域事件时，还需要做以下配置。将领域事件集成到 room 模块中。\n * <p>\n * 启动项. see <a href=\"https://iohao.github.io/game/docs/core/runner\">文档 - Runner 扩展机制</a>\n * <pre>{@code\n * public class MyRoomDomainRunner implements Runner {\n *     @Override\n *     public void onStart(BarSkeleton skeleton) {\n *         // 领域事件上下文参数\n *         DomainEventContextParam contextParam = new DomainEventContextParam();\n *         // 配置领域事件 - 玩法操作相关\n *         contextParam.addEventHandler(new OperationContextEventHandler());\n *         // 游戏流程相关\n *         contextParam.addEventHandler(new GameFlowEventHandler());\n *\n *         // 启动事件驱动\n *         DomainEventContext domainEventContext = new DomainEventContext(contextParam);\n *         domainEventContext.startup();\n *     }\n * }\n *\n * // 业务框架构建器\n * BarSkeletonBuilder builder = ...;\n * // 启动项\n * builder.addRunner(new MyRoomDomainRunner());\n * }\n * </pre>\n * <p>\n * OperationContext 玩法操作上下文领域事件，用于规避并发\n * <pre>{@code\n * // 创建玩法操作上下文\n * OperationContext operationContext = OperationContext.of(room, operationHandler)\n *     // 当前操作的玩家\n *     .setFlowContext(flowContext)\n *     // 开发者根据游戏业务定制的操作数据\n *     .setCommand(command);\n *\n * // 领域事件相关，https://iohao.github.io/game/docs/extension_module/domain_event\n * DomainEventPublish.send(operationContext);\n * }\n * </pre>\n * <p>\n * GameFlowEo，可规避 GameFlowService 中的并发问题\n * <pre>{@code\n * // 发送领域事件\n * GameFlowContext context = GameFlowContext.of(room, flowContext);\n * new GameFlowEo(flowContext, () -> {\n *     // 进入房间\n *     this.roomService.enterRoom(context);\n * }).send();\n *\n * GameFlowContext gameFlowContext = GameFlowContext.of(room, flowContext);\n * new GameFlowEo(flowContext, () -> {\n *     // 退出房间\n *     this.roomService.quitRoom(gameFlowContext);\n * }).send();\n *\n * GameFlowContext context = GameFlowContext.of(room, flowContext);\n * new GameFlowEo(flowContext, () -> {\n *     // 开始游戏\n *     this.roomService.startGame(context);\n * }).send();\n *\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-05-15\n * @since 21.8\n */\npackage com.iohao.game.widget.light.room.domain;"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/GameFixedService.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\n/**\n * 游戏流程 - 相对固定的流程。如，创建房间、创建玩家、进入房间；解散房间、退出房间、玩家准备。\n * <pre>\n *     创建房间\n *     创建玩家\n *     进入房间\n *\n *     解散房间\n *     退出房间\n *     玩家准备\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-05-15\n * @since 21.8\n */\npublic interface GameFixedService extends RoomCreator, PlayerCreator {\n    /**\n     * 进入房间 (重连)\n     *\n     * @param gameFlowContext 进入房间上下文\n     */\n    void enterRoom(GameFlowContext gameFlowContext);\n\n    /**\n     * 解散房间\n     *\n     * @param gameFlowContext gameFlowContext\n     * @deprecated non\n     */\n    default void dissolveRoom(GameFlowContext gameFlowContext) {\n    }\n\n    /**\n     * 玩家退出房间\n     *\n     * @param gameFlowContext gameFlowContext\n     */\n    default void quitRoom(GameFlowContext gameFlowContext) {\n    }\n\n    /**\n     * 玩家准备\n     *\n     * @param ready           true 表示准备，false 则是取消准备\n     * @param gameFlowContext gameFlowContext\n     */\n    default void ready(boolean ready, GameFlowContext gameFlowContext) {\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/GameFlowContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.common.kit.attr.AttrOption;\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport com.iohao.game.widget.light.room.Player;\nimport com.iohao.game.widget.light.room.Room;\n\n/**\n * 上下文 - 游戏流程上下文。\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @see GameFlowService\n * @since 21.8\n */\npublic interface GameFlowContext {\n    /**\n     * 获取动态成员属性\n     *\n     * @return 动态成员属性\n     */\n    AttrOptions getOptions();\n\n    /**\n     * get room\n     *\n     * @param <T> t\n     * @return room\n     */\n    <T extends Room> T getRoom();\n\n    /**\n     * get FlowContext\n     *\n     * @return FlowContext\n     */\n    FlowContext getFlowContext();\n\n    /**\n     * get 当前操作的玩家\n     *\n     * @param <T> t\n     * @return 当前玩家\n     */\n    default <T extends Player> T getPlayer() {\n        long userId = getUserId();\n        Room room = getRoom();\n        return room.getPlayerById(userId);\n    }\n\n    /**\n     * get userId\n     *\n     * @return userId\n     */\n    default long getUserId() {\n        return this.getFlowContext().getUserId();\n    }\n\n    /**\n     * get roomId\n     *\n     * @return roomId\n     */\n    default long getRoomId() {\n        return getRoom().getRoomId();\n    }\n\n    /**\n     * get 动态属性，获取选项值，如果选项不存在，返回默认值。\n     *\n     * @param option 选项值\n     * @return 如果 option 不存在，则使用默认的 option 值。\n     */\n    default <T> T option(AttrOption<T> option) {\n        return this.getOptions().option(option);\n    }\n\n    /**\n     * 设置动态属性。设置一个具有特定值的新选项，使用 null 值删除前一个设置的 {@link AttrOption}。\n     *\n     * @param option 选项值\n     * @param value  选项值, null 用于删除前一个 {@link AttrOption}.\n     * @return this\n     */\n    default <T> GameFlowContext option(AttrOption<T> option, T value) {\n        this.getOptions().option(option, value);\n        return this;\n    }\n\n    /**\n     * 创建 GameFlowContext（框架内置的默认实现）\n     *\n     * @param room        房间\n     * @param flowContext flowContext 当前玩家\n     * @return GameFlowContext\n     */\n    static GameFlowContext of(Room room, FlowContext flowContext) {\n        return new SimpleGameFlowContext(room, flowContext, flowContext.getUserId());\n    }\n\n    /**\n     * 创建 GameFlowContext（框架内置的默认实现）\n     *\n     * @param room 房间\n     * @return GameFlowContext\n     */\n    static GameFlowContext of(Room room) {\n        return of(room, 0);\n    }\n\n    /**\n     * 创建 GameFlowContext（框架内置的默认实现）\n     *\n     * @param room   房间\n     * @param userId userId\n     * @return GameFlowContext\n     */\n    static GameFlowContext of(Room room, long userId) {\n        return new SimpleGameFlowContext(room, null, userId);\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/GameFlowService.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\n/**\n * 游戏流程 - 相关扩展接口的聚合。如，创建房间、创建玩家、进入房间；解散房间、退出房间、玩家准备、开始游戏。\n * <pre>\n *     创建房间\n *     创建玩家\n *     进入房间\n *\n *     解散房间\n *     退出房间\n *     玩家准备\n *\n *     游戏开始\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\npublic interface GameFlowService extends GameFixedService, GameStartService {\n\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/GameStartService.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\n/**\n * 游戏流程 - 开始游戏相关。验证及验证通过之后的执行\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\npublic interface GameStartService {\n\n    /**\n     * 游戏开始前的逻辑校验\n     *\n     * <pre>\n     *     比如做一个游戏，房间空间大小为 10 人。\n     *     表示房间最大可容纳 10 人，而开始游戏并不一定需要满足 10 人。\n     *     现在，假设规则定义为满足 4 人准备，就可以开始游戏，那么这个开始前就可以派上用场了。\n     *\n     *     方法主要作用是交给子类游戏来定义开始游戏的规则，及一些其他规则验证。\n     * </pre>\n     *\n     * @param gameFlowContext 开始游戏上下文\n     */\n    default void startGameVerify(GameFlowContext gameFlowContext) {\n    }\n\n    /**\n     * 游戏开始，会在 {@link GameStartService#startGameVerify(GameFlowContext)} 校验成功后执行。\n     * <pre>\n     *     比如，斗地主、桌游、麻将 等可以发牌；\n     *     回合制游戏进入战斗；\n     * </pre>\n     *\n     * @param gameFlowContext 开始游戏上下文\n     */\n    default void startGameVerifyAfter(GameFlowContext gameFlowContext) {\n    }\n\n    /**\n     * 执行游戏开始，内部会调用 {@link GameStartService#startGameVerify(GameFlowContext)}\n     * 和 {@link GameStartService#startGameVerifyAfter(GameFlowContext)} 方法。\n     *\n     * @param gameFlowContext gameFlowContext\n     */\n    default void startGame(GameFlowContext gameFlowContext) {\n        this.startGameVerify(gameFlowContext);\n        this.startGameVerifyAfter(gameFlowContext);\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/PlayerCreator.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\nimport com.iohao.game.widget.light.room.Player;\n\n/**\n * @author 渔民小镇\n * @date 2025-06-03\n * @since 21.28\n */\npublic interface PlayerCreator {\n    /**\n     * 创建房间内的玩家\n     * <pre>\n     *     延迟到子游戏中实现, 以便适应不同的子游戏规则\n     * </pre>\n     *\n     * @param gameFlowContext 游戏流程上下文\n     * @return 玩家\n     */\n    Player createPlayer(GameFlowContext gameFlowContext);\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomCreateContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General  License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General  License for more details.\n *\n * You should have received a copy of the GNU Affero General  License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.common.kit.attr.AttrOption;\nimport com.iohao.game.common.kit.attr.AttrOptions;\n\n/**\n * 上下文 - 创建房间信息（及玩法规则）\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\npublic interface RoomCreateContext {\n    /**\n     * get 游戏 id\n     *\n     * @return 游戏 id\n     */\n    long getGameId();\n\n    /**\n     * setGameId\n     *\n     * @param gameId 游戏 id\n     */\n    RoomCreateContext setGameId(long gameId);\n\n    /**\n     * get 房间创建者\n     *\n     * @return 房间创建者\n     */\n    long getCreatorUserId();\n\n    /**\n     * get 房间空间大小\n     *\n     * @return 房间空间大小\n     */\n    int getSpaceSize();\n\n    /**\n     * get 开始游戏需要的最小人数\n     *\n     * @return 开始游戏需要的最小人数\n     */\n    int getStartGameMinSpaceSize();\n\n    /**\n     * 设置房间空间大小\n     *\n     * @param spaceSize 房间空间\n     * @return this\n     */\n    default RoomCreateContext setSpaceSize(int spaceSize) {\n        return setSpaceSize(spaceSize, spaceSize);\n    }\n\n    /**\n     * 设置房间空间大小\n     * <pre>\n     *     如 spaceSize = 10，表示房间最大可容纳 10 人，而开始游戏并不一定需要满足 10 人。\n     *     而 startGameMinSpaceSize 则表示开始游戏的前提条件所需要的最小人数，\n     *     当 startGameMinSpaceSize = 2 时，则表示当房间内有 2 个玩家时就能开始游戏。\n     * </pre>\n     *\n     * @param spaceSize             房间空间\n     * @param startGameMinSpaceSize 开始游戏需要的最小人数\n     * @return this\n     */\n    RoomCreateContext setSpaceSize(int spaceSize, int startGameMinSpaceSize);\n\n    /**\n     * 获取动态成员属性\n     *\n     * @return 动态成员属性\n     */\n    AttrOptions getOptions();\n\n    /**\n     * get 动态属性，获取选项值，如果选项不存在，返回默认值。\n     *\n     * @param option 选项值\n     * @return 如果 option 不存在，则使用默认的 option 值。\n     */\n    default <T> T option(AttrOption<T> option) {\n        return this.getOptions().option(option);\n    }\n\n    /**\n     * 设置动态属性。设置一个具有特定值的新选项，使用 null 值删除前一个设置的 {@link AttrOption}。\n     *\n     * @param option 选项值\n     * @param value  选项值, null 用于删除前一个 {@link AttrOption}.\n     * @return this\n     */\n    default <T> RoomCreateContext option(AttrOption<T> option, T value) {\n        this.getOptions().option(option, value);\n        return this;\n    }\n\n    /**\n     * 创建一个 RoomCreateContext 对象，使用默认实现\n     *\n     * @param creatorUserId creatorUserId 房间创建者\n     * @return default RoomCreateContext\n     */\n    static RoomCreateContext of(long creatorUserId) {\n        return new SimpleRoomCreateContext(creatorUserId);\n    }\n\n    /**\n     * 创建一个 RoomCreateContext 对象，使用默认实现\n     *\n     * @param flowContext flowContext 房间创建者\n     * @return default RoomCreateContext\n     */\n    static RoomCreateContext of(FlowContext flowContext) {\n        long userId = flowContext.getUserId();\n        return of(userId);\n    }\n\n    /**\n     * 创建一个 RoomCreateContext 对象，使用默认实现\n     *\n     * @return default RoomCreateContext\n     */\n    static RoomCreateContext of() {\n        return of(0);\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomCreator.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\nimport com.iohao.game.widget.light.room.Room;\n\n/**\n * @author 渔民小镇\n * @date 2025-06-03\n * @since 21.28\n */\npublic interface RoomCreator {\n    /**\n     * 创建房间, 子类只需要关心房间配置和规则信息\n     * <pre>\n     *     延迟到子游戏中实现, 以便适应不同的子游戏规则\n     * </pre>\n     *\n     * @param createContext 创建房间信息（及玩法规则）\n     * @return 房间\n     */\n    Room createRoom(RoomCreateContext createContext);\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/SimpleGameFlowContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport com.iohao.game.widget.light.room.Room;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\n\n/**\n * 上下文 - 游戏流程上下文（内置实现）\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class SimpleGameFlowContext implements GameFlowContext {\n    final Room room;\n    final FlowContext flowContext;\n    final long userId;\n\n    AttrOptions options;\n\n    SimpleGameFlowContext(Room room, FlowContext flowContext, long userId) {\n        this.room = room;\n        this.flowContext = flowContext;\n        this.userId = userId;\n    }\n\n    public AttrOptions getOptions() {\n        if (Objects.isNull(options)) {\n            this.options = new AttrOptions();\n        }\n\n        return options;\n    }\n\n    @Override\n    public long getUserId() {\n        return userId;\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/SimpleRoomCreateContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.flow;\n\nimport com.iohao.game.common.kit.attr.AttrOptions;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.Objects;\n\n/**\n * 上下文 - 创建房间信息及玩法规则（内置实现）\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\n@Getter\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class SimpleRoomCreateContext implements RoomCreateContext {\n    final long creatorUserId;\n\n    long gameId;\n    int spaceSize;\n    int startGameMinSpaceSize;\n    AttrOptions options;\n\n    public AttrOptions getOptions() {\n        if (Objects.isNull(options)) {\n            this.options = new AttrOptions();\n        }\n\n        return options;\n    }\n\n    public RoomCreateContext setSpaceSize(int spaceSize, int startGameMinSpaceSize) {\n        this.spaceSize = spaceSize;\n        this.startGameMinSpaceSize = startGameMinSpaceSize;\n        return this;\n    }\n\n    SimpleRoomCreateContext(long creatorUserId) {\n        this.creatorUserId = creatorUserId;\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - 桌游类、房间类游戏 - 子游戏自定义游戏流程相关的扩展。如，创建房间、创建玩家、进入房间；解散房间、退出房间、玩家准备、开始游戏。\n * <pre>\n *     只提供抽象流程, 具体的逻辑实现由子游戏自定义\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\npackage com.iohao.game.widget.light.room.flow;"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.operation;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.widget.light.domain.event.message.Eo;\nimport com.iohao.game.widget.light.room.Room;\nimport com.iohao.game.widget.light.room.domain.OperationContextEventHandler;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\n\n/**\n * 玩家玩法操作上下文\n * <pre>{@code\n * // 创建玩法操作上下文\n * OperationContext operationContext = OperationContext.of(room, operationHandler)\n *     // 当前操作的玩家\n *     .setFlowContext(flowContext)\n *     // 开发者根据游戏业务定制的操作数据\n *     .setCommand(command);\n * // 执行玩法操作\n * operationContext.execute();\n * }\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @see OperationContextEventHandler\n * @since 21.8\n */\n@Getter\n@Setter\n@Accessors(chain = true)\npublic class OperationContext implements PlayerOperationContext, Eo {\n    /** room */\n    final Room room;\n    /** 玩法操作业务类 */\n    final OperationHandler operationHandler;\n    /** 具体玩法需要操作的数据，通常由开发者根据游戏业务来定制 */\n    Object command;\n    /** 当前操作玩家的 FlowContext */\n    FlowContext flowContext;\n\n    OperationContext(Room room, OperationHandler operationHandler) {\n        this.room = room;\n        this.operationHandler = operationHandler;\n    }\n\n    /**\n     * 执行玩家的玩法操作，包括验证与处理。\n     */\n    public void execute() {\n        // 玩法操作业务类，将验证与操作分离\n        if (this.operationHandler.processVerify(this)) {\n            this.operationHandler.verify(this);\n            this.operationHandler.process(this);\n        }\n    }\n\n    /**\n     * 创建 OperationContext 对象\n     *\n     * @param room             房间\n     * @param operationHandler 玩法操作业务接口\n     * @return OperationContext 玩法操作上下文\n     */\n    public static OperationContext of(Room room, OperationHandler operationHandler) {\n        return new OperationContext(room, operationHandler);\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationFactory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.operation;\n\nimport com.iohao.game.common.kit.OperationCode;\n\nimport java.util.Optional;\n\n/**\n * 玩法操作工厂\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\npublic interface OperationFactory {\n    /**\n     * 获取 OperationHandler（玩法操作业务类）\n     *\n     * @param operation 操作码\n     * @return 操作码对应的业务逻辑处理类\n     */\n    OperationHandler getOperationHandler(int operation);\n\n    /**\n     * 获取玩家可操作的 OperationHandler（玩法操作业务类）\n     *\n     * @param operation 操作码\n     * @return 玩法操作业务类\n     */\n    OperationHandler getUserOperationHandler(int operation);\n\n    /**\n     * 将操作码与 OperationHandler（玩法操作业务类）关联\n     *\n     * @param operation        操作码\n     * @param operationHandler 玩法操作业务类\n     */\n    void mapping(int operation, OperationHandler operationHandler);\n\n    /**\n     * 玩家可操作的 OperationHandler。将操作码与 OperationHandler（玩法操作业务类）关联\n     *\n     * @param operation        操作码\n     * @param operationHandler 玩法操作业务类\n     */\n    void mappingUser(int operation, OperationHandler operationHandler);\n\n    /**\n     * 玩家可操作的 OperationHandler。将操作码与 OperationHandler（玩法操作业务类）关联\n     *\n     * @param operationCode    操作码\n     * @param operationHandler 玩法操作业务类\n     * @since 21.23\n     */\n    default void mappingUser(OperationCode operationCode, OperationHandler operationHandler) {\n        this.mappingUser(operationCode.getOperationCode(), operationHandler);\n    }\n\n    /**\n     * 将操作码与 OperationHandler（玩法操作业务类）关联\n     *\n     * @param operationCode    操作码\n     * @param operationHandler 玩法操作业务类\n     * @since 21.23\n     */\n    default void mapping(OperationCode operationCode, OperationHandler operationHandler) {\n        this.mapping(operationCode.getOperationCode(), operationHandler);\n    }\n\n    /**\n     * 通过操作码得到 OperationHandler Optional\n     *\n     * @param operation 操作码\n     * @return Optional OperationHandler\n     */\n    Optional<OperationHandler> optionalOperationHandler(int operation);\n\n    /**\n     * 创建 OperationFactory 对象（框架提供的内置实现）\n     *\n     * @return OperationFactory 对象\n     */\n    static OperationFactory of() {\n        return new SimpleOperationFactory();\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.operation;\n\n/**\n * 玩法操作业务接口，将验证与操作分离。\n * <pre>\n *     坦克:\n *         射子弹、发射导弹 等操作\n *\n *     麻将：\n *         出牌、碰牌、胡牌等操作\n *\n *     斗地主：\n *         出牌等操作\n *\n *     回合制的：\n *         攻击等\n *\n *     桌游：\n *         发牌等\n *         出牌等\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\npublic interface OperationHandler {\n    /**\n     * 检测验证, 验证用户操作步骤是否合法\n     *\n     * @param context 操作上下文\n     * @deprecated see {@link #processVerify(PlayerOperationContext)}\n     */\n    @Deprecated\n    default void verify(PlayerOperationContext context) {\n    }\n\n    /**\n     * 检测验证，验证用户操作步骤是否合法，通过返回值来决定是否执行 {@link OperationHandler#process(PlayerOperationContext)} 方法。\n     * <p>\n     * 当返回 false 时，不会执行 process 方法，相当于丢弃该请求的处理。\n     * 该方法与 {@link OperationHandler#verify(PlayerOperationContext)} 类似，\n     * 只不过多了一个返回值来决定是否执行 process 方法。\n     *\n     * @param context 操作上下文\n     * @return 当返回 true 时，会执行 {@link OperationHandler#process(PlayerOperationContext)} 方法\n     * @since 21.23\n     */\n    default boolean processVerify(PlayerOperationContext context) {\n        return true;\n    }\n\n    /**\n     * 验证通过后, 执行处理\n     *\n     * @param context 操作上下文\n     */\n    void process(PlayerOperationContext context);\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationService.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.operation;\n\nimport com.iohao.game.common.kit.OperationCode;\n\n/**\n * 玩法操作相关服务。获取 user 的玩法操作、所有玩法操作、玩法操作工厂。\n *\n * @author 渔民小镇\n * @date 2024-05-12\n * @since 21.8\n */\npublic interface OperationService {\n    /**\n     * @return 玩法操作工厂\n     */\n    OperationFactory getOperationFactory();\n\n    /**\n     * 获取 OperationHandler（玩法操作业务类）\n     *\n     * @param operation 操作码\n     * @return 操作码对应的业务逻辑处理类\n     */\n    default OperationHandler getOperationHandler(int operation) {\n        return this.getOperationFactory().getOperationHandler(operation);\n    }\n\n    /**\n     * 获取玩家可操作的 OperationHandler（玩法操作业务类）\n     *\n     * @param operation 操作码\n     * @return 玩法操作业务类\n     */\n    default OperationHandler getUserOperationHandler(int operation) {\n        return this.getOperationFactory().getUserOperationHandler(operation);\n    }\n\n    default OperationHandler getOperationHandler(OperationCode operationCode) {\n        return this.getOperationHandler(operationCode.getOperationCode());\n    }\n\n    default OperationHandler getUserOperationHandler(OperationCode operationCode) {\n        return this.getUserOperationHandler(operationCode.getOperationCode());\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/PlayerOperationContext.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.operation;\n\nimport com.iohao.game.action.skeleton.core.flow.FlowContext;\nimport com.iohao.game.widget.light.room.Player;\nimport com.iohao.game.widget.light.room.Room;\n\n/**\n * 上下文 - 玩家玩法操作上下文接口\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @see OperationHandler\n * @since 21.8\n */\npublic interface PlayerOperationContext {\n    /**\n     * get 房间\n     *\n     * @param <T> Room\n     * @return 房间\n     */\n    <T extends Room> T getRoom();\n\n    /**\n     * get 操作数据。具体玩法需要操作的数据，通常由开发者根据游戏业务来定制\n     *\n     * @param <T> t\n     * @return 操作数据\n     */\n    <T> T getCommand();\n\n    /**\n     * 当前玩家的 FlowContext\n     *\n     * @return FlowContext\n     */\n    FlowContext getFlowContext();\n\n    /**\n     * get 当前操作玩家的 userId\n     * <pre>\n     *     注意：{@link OperationContext#getFlowContext()} 必须存在\n     * </pre>\n     *\n     * @return userId\n     */\n    default long getUserId() {\n        return this.getFlowContext().getUserId();\n    }\n\n    /**\n     * get 当前操作的玩家\n     * <pre>\n     *     注意：{@link OperationContext#getFlowContext()} 必须存在\n     * </pre>\n     *\n     * @param <T> Player\n     * @return 当前操作的玩家\n     */\n    default <T extends Player> T getPlayer() {\n        Room room = this.getRoom();\n        long userId = this.getUserId();\n        return room.getPlayerById(userId);\n    }\n\n    /**\n     * get room id\n     *\n     * @return room id\n     */\n    default long getRoomId() {\n        return this.getRoom().getRoomId();\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/SimpleOperationFactory.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.operation;\n\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport lombok.AccessLevel;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.Map;\nimport java.util.Optional;\n\n/**\n * 玩法操作工厂（享元）实现类（内置实现）\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\nfinal class SimpleOperationFactory implements OperationFactory {\n    /**\n     * 操作处理\n     * <pre>\n     *     key : 操作码\n     *     value : 操作码对应的业务逻辑处理类\n     * </pre>\n     */\n    final Map<Integer, OperationHandler> operationMap = new NonBlockingHashMap<>();\n    /**\n     * 玩家可操作的操作处理\n     * <pre>\n     *     key : 操作码\n     *     value : 操作码对应的业务逻辑处理类\n     * </pre>\n     */\n    final Map<Integer, OperationHandler> userOperationMap = new NonBlockingHashMap<>();\n\n    public OperationHandler getUserOperationHandler(int operation) {\n        return this.userOperationMap.get(operation);\n    }\n\n    public OperationHandler getOperationHandler(int operation) {\n        return this.operationMap.get(operation);\n    }\n\n    public void mapping(int operation, OperationHandler operationHandler) {\n        if (this.operationMap.containsKey(operation)) {\n            ThrowKit.ofRuntimeException(\"operation already exists : \" + operation);\n        }\n\n        this.operationMap.put(operation, operationHandler);\n    }\n\n    public void mappingUser(int operation, OperationHandler operationHandler) {\n        this.mapping(operation, operationHandler);\n\n        if (this.userOperationMap.containsKey(operation)) {\n            ThrowKit.ofRuntimeException(\"operation already exists : \" + operation);\n        }\n\n        this.userOperationMap.put(operation, operationHandler);\n    }\n\n    public Optional<OperationHandler> optionalOperationHandler(int operation) {\n        return Optional.ofNullable(this.operationMap.get(operation));\n    }\n\n    SimpleOperationFactory() {\n    }\n}\n"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/SimpleOperationHandler.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.room.operation;\n\n/**\n * 执行任务{@link Runnable}，任务必须放到 {@link OperationContext#setCommand(Object)} 中。\n *\n * @author 渔民小镇\n * @date 2024-12-09\n * @since 21.23\n */\npublic final class SimpleOperationHandler implements OperationHandler {\n    @Override\n    public void process(PlayerOperationContext context) {\n        if (context.getCommand() instanceof Runnable runnable) {\n            runnable.run();\n        }\n    }\n\n    private SimpleOperationHandler() {\n    }\n\n    public static SimpleOperationHandler me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final SimpleOperationHandler ME = new SimpleOperationHandler();\n    }\n}"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - 桌游类、房间类游戏 - 房间内的玩法操作扩展。\n * <p>\n * 玩法操作业务 - 设计模式: 策略模式 + 享元模式\n * <pre>\n *     策略模式:\n *         定义一个接口，在写两个实现类并实现这个接口，这样就可以使用一个接口，在需要的时候，在根据情况使用哪一个实现类\n *     享元模式:\n *         维护 玩法接口的实现类实例 {@link com.iohao.game.widget.light.room.operation.OperationHandler}\n *\n *         将许多\"虚拟\"对象的状态集中管理, 减少运行时对象实例个数，节省内存\n * </pre>\n * 使用示例 - 配置玩法操作\n * <pre>{@code\n * // 创建 OperationFactory 对象（框架提供的内置实现）\n * OperationFactory factory = OperationFactory.of();\n *\n * // 配置玩法操作\n * factory.mappingUser(1, new ShootOperationHandler());\n *\n * // 如果有还有更多的操作，可以继续配置。\n * // 这里使用伪代码来举一个麻将的例子，配置吃、碰、杠的玩法操作\n * factory.mappingUser(10, new ChiOperationHandler());\n * factory.mappingUser(11, new PengOperationHandler());\n * factory.mappingUser(12, new GangOperationHandler());\n * }</pre>\n *\n * @author 渔民小镇\n * @date 2022-03-31\n * @since 21.8\n */\npackage com.iohao.game.widget.light.room.operation;"
  },
  {
    "path": "widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 扩展模块 - <a href=\"https://iohao.github.io/game/docs/extension_module/room\">桌游类、房间类游戏</a>，light-game-room + 领域事件 + 内置 Kit  = 轻松搞定桌游类游戏\n * <p>\n * 介绍\n * <pre>\n * 该模块是桌游类、房间类游戏的解决方案。比较适合桌游类、房间类的游戏基础搭建，基于该模型可以做一些如，炉石传说、三国杀、斗地主、麻将 ...等类似的桌游。\n * 或者说只要是房间类的游戏，该模型都适用。比如，CS、泡泡堂、飞行棋、坦克大战 ...等。\n *\n * 如果你计划做一些桌游类的游戏，那么推荐你基于该模块做扩展。该模块遵循面向对象的设计原则，没有强耦合，可扩展性强。\n * 该模块帮助开发者屏蔽了很多重复性的工作，并可为项目中的功能模块结构、开发流程等进行清晰的组织定义，减少了后续的项目维护成本。\n * </pre>\n * <p>\n * 主要解决的问题与职责\n * <pre>\n * 桌游、房间类的游戏在功能职责上可以分为 3 大类，分别是\n * 1. 房间管理相关的\n *   a. 管理着所有的房间、查询房间列表、房间的添加、房间的删除、房间与玩家之间的关联、房间查找（通过 roomId 查找、通过 userId 查找）。\n * 2. 开始游戏流程相关的\n *   a. 通常桌游、房间类的游戏都有一些固定的流程，如创建房间、玩家进入房间、玩家退出房间、解散房间、玩家准备、开始游戏 ...等。\n *   b. 开始游戏时，需要做开始前的验证，如房间内的玩家是否符足够 ...等，当一切符合业务时，才是真正的开始游戏。\n * 3. 玩法操作相关的\n *   a. 游戏开始后，由于不同游戏之间的具体操作是不相同的。如坦克的射击，炉石的战前选牌、出牌，麻将的吃、碰、杠、过、胡，回合制游戏的普攻、防御、技能 ...等。\n *   b. 由于玩法操作的不同，所以我们的玩法操作需要是可扩展的，并用于处理具体的玩法操作。同时这种扩展方式更符合单一职责，使得我们后续的扩展与维护成本更低。\n *\n * 以上功能职责（房间管理相关、流程相关、玩法操作相关）属于相对通用的功能。如果每款游戏都重复的做这些工作，除了枯燥之外，还将浪费巨大的人力成本。\n *\n * 而当前模块则能很好的帮助开发者屏蔽这些重复性的工作，并可为项目中的功能模块结构、开发流程等进行清晰的组织定义，减少了后续的项目维护成本。\n * 更重要的是有相关文档，将来当你的团队有新进成员时，可以快速的上手。\n * </pre>\n *\n * @author 渔民小镇\n * @date 2024-05-14\n * @since 21.8\n */\npackage com.iohao.game.widget.light.room;"
  },
  {
    "path": "widget/light-jprotobuf/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>light-jprotobuf</artifactId>\n    <name>light-jprotobuf for ioGame</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.iohao.game</groupId>\n            <artifactId>common-kit</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/com.thoughtworks.qdox/qdox -->\n        <dependency>\n            <groupId>com.thoughtworks.qdox</groupId>\n            <artifactId>qdox</artifactId>\n            <version>${qdox.version}</version>\n        </dependency>\n\n    </dependencies>\n</project>"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/FieldNameGenerate.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * FieldNameGenerate\n *\n * @author 渔民小镇\n * @date 2024-10-30\n * @since 21.20\n */\n@Setter(value = AccessLevel.PACKAGE)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic final class FieldNameGenerate {\n    boolean enumType;\n    @Getter\n    String fieldName;\n\n    public boolean isEnum() {\n        return enumType;\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoFieldTypeHolder.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport lombok.AccessLevel;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-24\n */\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Accessors(chain = true)\npublic class ProtoFieldTypeHolder {\n\n    final Map<Class<?>, String> filedTypeMap = new HashMap<>();\n\n    public String getProtoType(Class<?> filedTypeClass) {\n        return filedTypeMap.get(filedTypeClass);\n    }\n\n    private void init() {\n        filedTypeMap.put(Double.class, \"double\");\n        filedTypeMap.put(double.class, \"double\");\n\n        filedTypeMap.put(Float.class, \"float\");\n        filedTypeMap.put(float.class, \"float\");\n\n        filedTypeMap.put(Long.class, \"int64\");\n        filedTypeMap.put(long.class, \"int64\");\n\n        filedTypeMap.put(Integer.class, \"int32\");\n        filedTypeMap.put(int.class, \"int32\");\n\n        filedTypeMap.put(Boolean.class, \"bool\");\n        filedTypeMap.put(boolean.class, \"bool\");\n\n        filedTypeMap.put(String.class, \"string\");\n\n\n        filedTypeMap.put(byte[].class, \"bytes\");\n\n    }\n\n    public ProtoFieldTypeHolder() {\n        init();\n    }\n\n    public static ProtoFieldTypeHolder me() {\n        return Holder.ME;\n    }\n\n    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */\n    private static class Holder {\n        static final ProtoFieldTypeHolder ME = new ProtoFieldTypeHolder();\n    }\n\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoFileMerge.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport java.lang.annotation.*;\n\n/**\n * pb 文件合并归类\n *\n * @author 渔民小镇\n * @date 2022-01-24\n */\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface ProtoFileMerge {\n    /** 原生 .proto 的文件名 */\n    String fileName();\n\n    /** 原生 .proto 的包名 package */\n    String filePackage();\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoFileValue.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-25\n */\npublic interface ProtoFileValue {\n    String getFileName();\n\n    String getFilePackage();\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoGenerateFile.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport com.iohao.game.common.kit.ArrayKit;\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.common.kit.exception.ThrowKit;\nimport com.iohao.game.common.kit.io.FileKit;\nimport lombok.AccessLevel;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.io.File;\nimport java.util.*;\nimport java.util.function.Consumer;\n\n/**\n * 生成 pb 文件\n *\n * @author 渔民小镇\n * @date 2022-01-25\n */\n@Slf4j\n@Setter\n@Accessors(chain = true)\n@FieldDefaults(level = AccessLevel.PRIVATE)\npublic class ProtoGenerateFile {\n    /** proto package path */\n    final Set<String> protoPackageSet = new HashSet<>();\n    /** pb 源码目录 */\n    String protoSourcePath;\n    /** 生成 proto file 目录 */\n    String generateFolder;\n\n    public ProtoGenerateFile addProtoPackage(Collection<String> protoPackageList) {\n        this.protoPackageSet.addAll(protoPackageList);\n        return this;\n    }\n\n    public ProtoGenerateFile addProtoPackage(String packageName) {\n        protoPackageSet.add(packageName);\n        return this;\n    }\n\n    private void checked() {\n        if (StrKit.isBlank(generateFolder)) {\n            String currentDir = System.getProperty(\"user.dir\");\n            this.generateFolder = ArrayKit.join(new String[]{currentDir, \"target\", \"proto\"}, File.separator);\n        }\n\n        FileKit.mkdir(this.generateFolder);\n\n        if (Objects.isNull(this.protoSourcePath)) {\n            this.protoSourcePath = System.getProperty(\"user.dir\");\n        }\n\n        if (protoPackageSet.isEmpty()) {\n            ThrowKit.ofRuntimeException(\"protoPackageSet is empty\");\n        }\n    }\n\n    public void generate() {\n        checked();\n\n        ProtoJavaAnalyse.getJavaProjectBuilder(protoSourcePath);\n\n        ProtoJavaAnalyse analyse = new ProtoJavaAnalyse();\n        Map<ProtoJavaRegionKey, ProtoJavaRegion> regionMap = new NonBlockingHashMap<>();\n\n        protoPackageSet.parallelStream().forEach(protoPackage -> {\n            // analyse protoPackage\n            regionMap.putAll(analyse.analyse(protoPackage, protoSourcePath));\n        });\n\n        Consumer<ProtoJavaRegion> javaRegionConsumer = javaRegion -> {\n            var fileName = javaRegion.getFileName();\n            var protoJavaList = javaRegion.getProtoJavaList();\n\n            String protoString = javaRegion.toProtoFile();\n\n            if (ProtoGenerateSetting.enableLog) {\n                log.info(\"\"\"\n                        ########## {} ########## protoSize:{}\n                        {}\n                        \"\"\", fileName, protoJavaList.size(), protoString);\n            }\n\n            String protoFilePath = StrKit.format(\"{}{}{}\"\n                    , this.generateFolder\n                    , File.separator\n                    , fileName\n            );\n\n            FileKit.writeUtf8String(protoString, protoFilePath);\n            log.info(\"\\nprotoFilePath: {}\", protoFilePath);\n        };\n\n        regionMap.values().forEach(javaRegionConsumer);\n    }\n\n    @Deprecated\n    public static ProtoGenerateFileBuilder builder() {\n        return new ProtoGenerateFileBuilder();\n    }\n\n    @Deprecated\n    public static class ProtoGenerateFileBuilder {\n        private final ProtoGenerateFile generateFile = new ProtoGenerateFile();\n        /** pb 源码目录 */\n        String protoSourcePath;\n        /** 生成 proto file 目录 */\n        String generateFolder;\n\n        public ProtoGenerateFileBuilder protoSourcePath(String protoSourcePath) {\n            this.protoSourcePath = protoSourcePath;\n            return this;\n        }\n\n        public ProtoGenerateFileBuilder generateFolder(String generateFolder) {\n            this.generateFolder = generateFolder;\n            return this;\n        }\n\n        @Deprecated\n        public ProtoGenerateFileBuilder protoPackagePath(String protoPackagePath) {\n            return this.addProtoPackage(protoPackagePath);\n        }\n\n        public ProtoGenerateFileBuilder addProtoPackage(String packageName) {\n            generateFile.protoPackageSet.add(packageName);\n            return this;\n        }\n\n        public ProtoGenerateFileBuilder addProtoPackage(Collection<String> protoPackageList) {\n            generateFile.protoPackageSet.addAll(protoPackageList);\n            return this;\n        }\n\n        public ProtoGenerateFile build() {\n            generateFile.generateFolder = this.generateFolder;\n            generateFile.protoSourcePath = this.protoSourcePath;\n\n            return generateFile;\n        }\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoGenerateSetting.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.UtilityClass;\n\nimport java.util.function.Function;\n\n/**\n * @author 渔民小镇\n * @date 2024-10-31\n * @since 21.20\n */\n@UtilityClass\npublic final class ProtoGenerateSetting {\n\n    public boolean enableLog = false;\n\n    @Setter\n    @Getter\n    Function<FieldNameGenerate, String> fieldNameFunction = fieldNameGenerate -> {\n        if (fieldNameGenerate.isEnum()) {\n            return fieldNameGenerate.getFieldName();\n        }\n\n        // default UnderScoreCase\n        StringBuilder result = new StringBuilder();\n        String fieldName = fieldNameGenerate.getFieldName();\n\n        for (int i = 0; i < fieldName.length(); i++) {\n            char c = fieldName.charAt(i);\n            if (Character.isUpperCase(c)) {\n                if (i > 0) {\n                    result.append('_');\n                }\n\n                result.append(Character.toLowerCase(c));\n            } else {\n                result.append(c);\n            }\n        }\n\n        return result.toString();\n    };\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoJava.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport com.iohao.game.common.kit.StrKit;\nimport com.thoughtworks.qdox.model.JavaClass;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-24\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Accessors(chain = true)\npublic class ProtoJava {\n    Class<?> clazz;\n    String className;\n\n    String comment;\n\n    String fileName;\n    String filePackage;\n\n    JavaClass javaClass;\n\n    List<ProtoJavaField> protoJavaFieldList = new ArrayList<>();\n\n    public void addProtoJavaFiled(ProtoJavaField protoJavaField) {\n        this.protoJavaFieldList.add(protoJavaField);\n    }\n\n    public boolean inThisFile(ProtoJava protoJava) {\n        return Objects.equals(this.fileName, protoJava.fileName) && Objects.equals(this.filePackage, protoJava.filePackage);\n    }\n\n    public ProtoJavaRegionKey getProtoJavaRegionKey() {\n        return new ProtoJavaRegionKey(this.fileName, this.filePackage);\n\n    }\n\n    public String toProtoMessage() {\n\n        String fieldsString = protoJavaFieldList\n                .stream()\n                .map(ProtoJavaField::toProtoFieldLine)\n                .collect(Collectors.joining(\"\\n\"));\n\n        Map<String, String> messageMap = new HashMap<>();\n        messageMap.put(\"className\", this.className);\n        messageMap.put(\"fieldsString\", fieldsString);\n        messageMap.put(\"classComment\", this.comment);\n        messageMap.put(\"classOrEnum\", clazz.isEnum() ? \"enum\" : \"message\");\n\n        String template = \"\"\"\n                // {classComment}\n                {classOrEnum} {className} {\n                {fieldsString}\n                }\n                                \n                \"\"\";\n\n        return StrKit.format(template, messageMap);\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoJavaAnalyse.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport com.baidu.bjf.remoting.protobuf.EnumReadable;\nimport com.baidu.bjf.remoting.protobuf.annotation.Ignore;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.esotericsoftware.reflectasm.FieldAccess;\nimport com.iohao.game.common.consts.CommonConst;\nimport com.iohao.game.common.kit.ClassScanner;\nimport com.iohao.game.common.kit.MoreKit;\nimport com.iohao.game.common.kit.StrKit;\nimport com.thoughtworks.qdox.JavaProjectBuilder;\nimport com.thoughtworks.qdox.model.JavaAnnotation;\nimport com.thoughtworks.qdox.model.JavaClass;\nimport com.thoughtworks.qdox.model.JavaField;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.io.File;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-25\n */\n@Slf4j\npublic class ProtoJavaAnalyse {\n    static final Map<String, JavaProjectBuilder> javaProjectBuilderMap = new NonBlockingHashMap<>();\n    final Map<ProtoJavaRegionKey, ProtoJavaRegion> protoJavaRegionMap = new NonBlockingHashMap<>();\n    final Map<Class<?>, ProtoJava> protoJavaMap = new NonBlockingHashMap<>();\n    final Map<String, JavaClass> protoJavaSourceFileMap = new NonBlockingHashMap<>();\n\n    public Map<ProtoJavaRegionKey, ProtoJavaRegion> analyse(String protoPackagePath, String protoSourcePath) {\n        return this.analyse(protoPackagePath, protoSourcePath, this.predicateFilter);\n    }\n\n    public Map<ProtoJavaRegionKey, ProtoJavaRegion> analyse(String protoPackagePath, String protoSourcePath, Predicate<Class<?>> predicateFilter) {\n        var javaProjectBuilder = getJavaProjectBuilder(protoSourcePath);\n        Collection<JavaClass> javaClassCollection = javaProjectBuilder.getClasses();\n\n        javaClassCollection.parallelStream().filter(javaClass -> {\n\n            List<JavaAnnotation> annotations = javaClass.getAnnotations();\n            if (annotations.size() < 2) {\n                return false;\n            }\n\n            long count = annotations.parallelStream().filter(annotation -> {\n                String string = annotation.getType().toString();\n                return string.contains(ProtobufClass.class.getName())\n                       || string.contains(ProtoFileMerge.class.getName());\n            }).count();\n\n            return count >= 2;\n        }).forEach(javaClass -> {\n            protoJavaSourceFileMap.put(javaClass.toString(), javaClass);\n\n            if (ProtoGenerateSetting.enableLog) {\n                log.info(\"javaClass: {}\", javaClass);\n            }\n        });\n\n        ClassScanner classScanner = new ClassScanner(protoPackagePath, predicateFilter);\n        List<Class<?>> classList = classScanner.listScan();\n\n        if (classList.isEmpty()) {\n            return protoJavaRegionMap;\n        }\n\n        List<ProtoJava> protoJavaList = this.convert(classList);\n        for (ProtoJava protoJava : protoJavaList) {\n            this.analyseField(protoJava);\n        }\n\n        return protoJavaRegionMap;\n    }\n\n    static JavaProjectBuilder getJavaProjectBuilder(String protoSourcePath) {\n        JavaProjectBuilder javaProjectBuilder = javaProjectBuilderMap.get(protoSourcePath);\n        if (javaProjectBuilder == null) {\n            var builder = new JavaProjectBuilder();\n            builder.setEncoding(StandardCharsets.UTF_8.name());\n            builder.addSourceTree(new File(protoSourcePath));\n            return MoreKit.putIfAbsent(javaProjectBuilderMap, protoSourcePath, builder);\n        }\n\n        return javaProjectBuilder;\n    }\n\n    private List<ProtoJava> convert(List<Class<?>> classList) {\n\n        return classList.stream().map(clazz -> {\n            ProtoFileMerge annotation = clazz.getAnnotation(ProtoFileMerge.class);\n            String fileName = annotation.fileName();\n            String filePackage = annotation.filePackage();\n            JavaClass javaClass = protoJavaSourceFileMap.get(clazz.toString());\n\n            ProtoJava protoJava = new ProtoJava()\n                    .setClassName(clazz.getSimpleName())\n                    .setComment(javaClass.getComment())\n                    .setClazz(clazz)\n                    .setFileName(fileName)\n                    .setFilePackage(filePackage)\n                    .setJavaClass(javaClass);\n\n            protoJavaMap.put(clazz, protoJava);\n\n            ProtoJavaRegionKey regionKey = new ProtoJavaRegionKey(fileName, filePackage);\n            ProtoJavaRegion protoJavaRegion = this.getProtoJavaRegion(regionKey);\n            protoJavaRegion.addProtoJava(protoJava);\n\n            return protoJava;\n        }).collect(Collectors.toList());\n    }\n\n    private void analyseField(ProtoJava protoJava) {\n        Class<?> clazz = protoJava.getClazz();\n        Field[] fields = clazz.isEnum()\n                ? Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.getType().isEnum()).toArray(Field[]::new)\n                : FieldAccess.get(clazz).getFields();\n\n        JavaClass javaClass = protoJava.getJavaClass();\n        // 枚举 enum 的下标从 0 开始，message 的下标从 1 开始\n        int order = clazz.isEnum() ? 0 : 1;\n        var enumConstants = clazz.isEnum() ? clazz.getEnumConstants() : CommonConst.emptyObjects;\n\n        for (int i = 0; i < fields.length; i++) {\n            var field = fields[i];\n            if (Objects.nonNull(field.getAnnotation(Ignore.class))) {\n                continue;\n            }\n\n            Class<?> fieldTypeClass = field.getType();\n            String fieldName = field.getName();\n            JavaField javaField = javaClass.getFieldByName(fieldName);\n\n            ProtoJavaField protoJavaField = new ProtoJavaField()\n                    .setRepeated(List.class.equals(fieldTypeClass))\n                    .setFieldName(fieldName)\n                    .setComment(javaField.getComment())\n                    .setOrder(order++)\n                    .setFieldTypeClass(fieldTypeClass)\n                    .setField(field)\n                    .setProtoJavaParent(protoJava);\n\n            // 自定义枚举值\n            if (clazz.isEnum() && EnumReadable.class.isAssignableFrom(clazz)) {\n                if (enumConstants[i] instanceof EnumReadable r) {\n                    protoJavaField.setOrder(r.value());\n                }\n            }\n\n            protoJava.addProtoJavaFiled(protoJavaField);\n\n            String fieldProtoType = ProtoFieldTypeHolder.me().getProtoType(fieldTypeClass);\n            if (Objects.nonNull(fieldProtoType)) {\n                protoJavaField.setFieldProtoType(fieldProtoType);\n                continue;\n            }\n\n            if (protoJavaField.isMap()) {\n                processMapFieldProtoJava(protoJavaField);\n            } else if (protoJavaField.isList()) {\n                processListFieldProtoJava(protoJavaField);\n            } else {\n                processFieldProtoJava(protoJavaField);\n            }\n        }\n    }\n\n    private String fieldProtoTypeToString(ProtoJavaField protoJavaField, Class<?> fieldTypeClass) {\n        String fieldName = protoJavaField.getFieldName();\n        // 这个字段是一个 proto 对象类型\n        ProtoJava protoJavaFieldType = this.getFieldProtoJava(fieldTypeClass, fieldName, protoJavaField);\n        ProtoJava protoJavaParent = protoJavaField.getProtoJavaParent();\n\n        String filePackage = protoJavaFieldType.getFilePackage();\n        String className = protoJavaFieldType.getClassName();\n\n        String fieldProtoType;\n\n        if (protoJavaParent.inThisFile(protoJavaFieldType)) {\n            // 同一个文件的 proto 对象\n            fieldProtoType = className;\n        } else {\n            ProtoJavaRegionKey regionKey = protoJavaParent.getProtoJavaRegionKey();\n            ProtoJavaRegion protoJavaRegion = this.getProtoJavaRegion(regionKey);\n            protoJavaRegion.addOtherProtoFile(protoJavaFieldType);\n            if (Objects.equals(protoJavaParent.getFilePackage(), filePackage)) {\n                // 不在同一个文件夹，但是在同一个包下\n                fieldProtoType = className;\n            } else {\n                // 不在同一个文件中\n                fieldProtoType = StrKit.format(\"{}.{}\", filePackage, className);\n            }\n        }\n\n        return fieldProtoType;\n    }\n\n    private void processFieldProtoJava(ProtoJavaField protoJavaField) {\n        // 这个字段是一个 proto 对象类型\n\n        Class<?> fieldTypeClass = protoJavaField.getFieldTypeClass();\n        String fieldName = protoJavaField.getFieldName();\n\n        if (Objects.isNull(protoJavaField.getComment())) {\n            ProtoJava protoJavaFieldType = this.getFieldProtoJava(fieldTypeClass, fieldName, protoJavaField);\n            protoJavaField.setComment(protoJavaFieldType.getComment());\n        }\n\n        String fieldProtoType = this.fieldProtoTypeToString(protoJavaField, fieldTypeClass);\n        protoJavaField.setFieldProtoType(fieldProtoType);\n    }\n\n    private void processListFieldProtoJava(ProtoJavaField protoJavaField) {\n        // 获取 map 的 <k,v> 类型\n        ParameterizedType genericType = (ParameterizedType) protoJavaField.getField().getGenericType();\n        Type[] actualTypeArguments = genericType.getActualTypeArguments();\n\n        Class<?> firstClass = (Class<?>) actualTypeArguments[0];\n        String fieldProtoType = ProtoFieldTypeHolder.me().getProtoType(firstClass);\n\n        if (Objects.isNull(fieldProtoType)) {\n            fieldProtoType = this.fieldProtoTypeToString(protoJavaField, firstClass);\n        }\n\n        protoJavaField\n                .setRepeated(true)\n                .setFieldProtoType(fieldProtoType)\n        ;\n    }\n\n    private void processMapFieldProtoJava(ProtoJavaField protoJavaField) {\n\n        Map<String, String> map = new HashMap<>();\n\n        // map 类型\n        Field field = protoJavaField.getField();\n\n        // 获取 map 的 <k,v> 类型\n        ParameterizedType genericType = (ParameterizedType) field.getGenericType();\n        Type[] actualTypeArguments = genericType.getActualTypeArguments();\n\n        Class<?> keyClass = (Class<?>) actualTypeArguments[0];\n        String keyFieldProtoType = ProtoFieldTypeHolder.me().getProtoType(keyClass);\n        map.put(\"keyStr\", keyFieldProtoType);\n\n        if (Objects.isNull(keyFieldProtoType)) {\n            // key 是一个 proto 对象类型\n            String keyStr = this.fieldProtoTypeToString(protoJavaField, keyClass);\n            map.put(\"keyStr\", keyStr);\n        }\n\n        Class<?> valueClass = (Class<?>) actualTypeArguments[1];\n        String valueFieldProtoType = ProtoFieldTypeHolder.me().getProtoType(valueClass);\n        map.put(\"valueStr\", valueFieldProtoType);\n        if (Objects.isNull(valueFieldProtoType)) {\n            // value 是一个 proto 对象类型\n            String valueStr = this.fieldProtoTypeToString(protoJavaField, valueClass);\n            map.put(\"valueStr\", valueStr);\n        }\n\n        String fieldProtoType = StrKit.format(\"map<{keyStr},{valueStr}>\", map);\n        protoJavaField.setFieldProtoType(fieldProtoType);\n    }\n\n    private ProtoJava getFieldProtoJava(Class<?> fieldTypeClass, String fieldName, ProtoJavaField protoJavaField) {\n\n        if (!predicateFilter.test(fieldTypeClass)) {\n\n            String templateErr = \"\"\"\n                    {}.{} class type not is protobuf !\n                    class must import annotation {}\n                    class must import annotation {}\n                    \"\"\";\n\n            String errorMsg = StrKit.format(templateErr\n                    , protoJavaField.getProtoJavaParent().getClassName()\n                    , fieldName\n                    , ProtobufClass.class\n                    , ProtoFileMerge.class\n            );\n\n            throw new RuntimeException(errorMsg);\n        }\n\n        return this.protoJavaMap.get(fieldTypeClass);\n    }\n\n    private final Predicate<Class<?>> predicateFilter = (clazz) -> {\n        ProtobufClass annotation = clazz.getAnnotation(ProtobufClass.class);\n        ProtoFileMerge protoFileMerge = clazz.getAnnotation(ProtoFileMerge.class);\n\n        return Objects.nonNull(annotation) && Objects.nonNull(protoFileMerge);\n    };\n\n    private ProtoJavaRegion getProtoJavaRegion(ProtoJavaRegionKey key) {\n        ProtoJavaRegion protoJavaRegion = protoJavaRegionMap.get(key);\n\n        if (Objects.isNull(protoJavaRegion)) {\n\n            protoJavaRegion = new ProtoJavaRegion();\n\n            protoJavaRegion = protoJavaRegionMap.putIfAbsent(key, protoJavaRegion);\n\n            if (Objects.isNull(protoJavaRegion)) {\n                protoJavaRegion = protoJavaRegionMap.get(key);\n            }\n\n            protoJavaRegion.setFileName(key.fileName());\n            protoJavaRegion.setFilePackage(key.filePackage());\n        }\n\n        return protoJavaRegion;\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoJavaField.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.widget.light.protobuf.kit.GenerateFileKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\n\nimport java.lang.reflect.Field;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-24\n */\n@Getter\n@Setter\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Accessors(chain = true)\npublic class ProtoJavaField {\n    boolean repeated;\n    String fieldName;\n    String comment;\n    int order;\n    Class<?> fieldTypeClass;\n    String fieldProtoType;\n    Field field;\n\n    ProtoJava protoJavaParent;\n\n    boolean isMap() {\n        return Map.class.equals(fieldTypeClass);\n    }\n\n    boolean isList() {\n        return List.class.equals(fieldTypeClass);\n    }\n\n    private Map<String, String> createParam() {\n        Map<String, String> messageMap = new HashMap<>();\n\n        messageMap.put(\"comment\", this.comment);\n        messageMap.put(\"repeated\", \"\");\n        messageMap.put(\"fieldProtoType\", this.fieldProtoType);\n        messageMap.put(\"order\", String.valueOf(this.order));\n\n        FieldNameGenerate fieldNameGenerate = new FieldNameGenerate();\n        fieldNameGenerate.setEnumType(this.protoJavaParent.getClazz().isEnum());\n        fieldNameGenerate.setFieldName(this.fieldName);\n        messageMap.put(\"fieldName\", ProtoGenerateSetting.getFieldNameFunction().apply(fieldNameGenerate));\n\n        if (this.repeated) {\n            messageMap.put(\"repeated\", \"repeated \");\n        }\n\n        return messageMap;\n    }\n\n    public String toProtoFieldLine() {\n        Map<String, String> messageMap = this.createParam();\n\n        String templateFiled = getTemplateFiled(this.protoJavaParent.getClazz().isEnum());\n\n        return StrKit.format(templateFiled, messageMap);\n    }\n\n    /**\n     * 生成proto文本模板\n     *\n     * @param fieldIsInEnum 该bool含义表示当前Field所在的类文件是否是枚举类型，取得是protoJavaParent的isEnum\n     * @return 如果是枚举类文件中，属性前面不用加{fieldProtoType}，如果枚举类型是在类文件中则加上{fieldProtoType}\n     */\n    private String getTemplateFiled(boolean fieldIsInEnum) {\n        StringBuilder templateFiled = new StringBuilder();\n\n        if (!Objects.isNull(this.comment)) {\n            templateFiled.append(\"\"\"\n                      // {comment}\n                    \"\"\");\n        }\n\n        if (fieldIsInEnum) {\n            templateFiled.append(\"  {repeated}{fieldName} = {order};\");\n        } else {\n            templateFiled.append(\"  {repeated}{fieldProtoType} {fieldName} = {order};\");\n        }\n\n        return templateFiled.toString();\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoJavaRegion.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport com.iohao.game.common.kit.StrKit;\nimport com.iohao.game.common.kit.time.FormatTimeKit;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport lombok.experimental.FieldDefaults;\nimport org.jctools.maps.NonBlockingHashMap;\n\nimport java.util.*;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-25\n */\n@Setter\n@Getter\n@FieldDefaults(level = AccessLevel.PRIVATE)\n@Accessors(chain = true)\npublic class ProtoJavaRegion {\n    String fileName;\n    String filePackage;\n\n    final Map<Class<?>, ProtoJava> protoJavaMap = new NonBlockingHashMap<>();\n    final List<ProtoJava> protoJavaList = new CopyOnWriteArrayList<>();\n    final ProtoJavaRegionHead regionHead = new ProtoJavaRegionHead();\n\n    public void addProtoJava(ProtoJava protoJava) {\n        this.protoJavaList.add(protoJava);\n        this.protoJavaMap.put(protoJava.getClazz(), protoJava);\n    }\n\n    public void addOtherProtoFile(ProtoJava protoJava) {\n        String fileName = protoJava.getFileName();\n        this.regionHead.fileNameSet.add(fileName);\n    }\n\n    static class ProtoJavaRegionHead {\n        Set<String> fileNameSet = new HashSet<>();\n        String filePackage;\n\n        private String toProtoHead() {\n\n            String templateFileName = \"\"\"\n                    import \"{}\";\n                    \"\"\";\n\n            StringBuilder fileNameBuilder = new StringBuilder();\n\n            for (String filePackage : fileNameSet) {\n                String filePackageString = StrKit.format(templateFileName, filePackage);\n                fileNameBuilder.append(filePackageString);\n            }\n\n            String templateHead = \"\"\"\n                    syntax = \"proto3\";\n                    package {};\n                    {}\n                    \"\"\";\n\n            return StrKit.format(templateHead, this.filePackage, fileNameBuilder.toString());\n        }\n    }\n\n    public String toProtoFile() {\n        this.regionHead.filePackage = this.filePackage;\n        String protoHead = this.regionHead.toProtoHead();\n\n        String firstLine = \"\"\"\n                // generate %s , protoSize: %s\n                // https://github.com/iohao/ioGame\n                \"\"\".formatted(FormatTimeKit.format(), protoJavaList.size());\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(firstLine);\n        builder.append(System.lineSeparator());\n        builder.append(protoHead);\n\n        this.protoJavaList.stream()\n                // 排序规则\n                .sorted(Comparator.comparing(ProtoJava::getClassName))\n                .map(ProtoJava::toProtoMessage)\n                .forEach(builder::append);\n\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/ProtoJavaRegionKey.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-25\n */\npublic record ProtoJavaRegionKey(String fileName, String filePackage) {\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/main/java/com/iohao/game/widget/light/protobuf/kit/GenerateFileKit.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf.kit;\n\nimport com.iohao.game.common.kit.ArrayKit;\nimport com.iohao.game.widget.light.protobuf.ProtoGenerateFile;\nimport lombok.experimental.UtilityClass;\n\nimport java.io.File;\n\n/**\n * proto 文件生成工具\n *\n * @author 渔民小镇\n * @date 2023-07-13\n */\n@UtilityClass\npublic class GenerateFileKit {\n    /**\n     * 生成 proto 文件\n     *\n     * @param protoPackagePath proto 类所在包名\n     * @param generateFolder   生成 proto file 的目录\n     */\n    public void generate(String protoPackagePath, String generateFolder) {\n        /*\n         * .proto 文件生成\n         *\n         * 运行该类，会在当前项目 target/proto 目录下生成 .proto 文件\n         */\n\n        String currentDir = System.getProperty(\"user.dir\");\n\n        ProtoGenerateFile protoGenerateFile = new ProtoGenerateFile()\n                // 源码目录\n                .setProtoSourcePath(currentDir)\n                // 生成 .proto 文件存放的目录\n                .setGenerateFolder(generateFolder)\n                // 需要扫描的包名\n                .addProtoPackage(protoPackagePath);\n\n        // 生成 .proto 文件\n        protoGenerateFile.generate();\n    }\n\n    /**\n     * 生成 proto 文件\n     *\n     * @param protoPackagePath proto 类所在包名\n     */\n    public void generate(String protoPackagePath) {\n        String currentDir = System.getProperty(\"user.dir\");\n\n        // 生成 .proto 文件存放的目录\n        String generateFolder = ArrayKit.join(new String[]{\n                currentDir\n                , \"target\"\n                , \"proto\"\n        }, File.separator);\n\n        generate(protoPackagePath, generateFolder);\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/ProtoJavaTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf;\n\nimport com.iohao.game.widget.light.protobuf.kit.GenerateFileKit;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-24\n */\n@Slf4j\npublic class ProtoJavaTest {\n    @Test\n    public void generate() {\n        /*\n         * .proto 文件生成\n         *\n         * 运行该类，会在当前项目 target/proto 目录下生成 .proto 文件\n         */\n\n        // 需要扫描的包名\n        String packagePath = ProtoJavaTest.class.getPackageName();\n        // .proto 文件生成\n        GenerateFileKit.generate(packagePath);\n    }\n}"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/data/AnimalTypeEnum.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf.data;\n\nimport com.baidu.bjf.remoting.protobuf.EnumReadable;\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.widget.light.protobuf.ProtoFileMerge;\nimport lombok.ToString;\n\n/**\n * TestAnimalTypeEnum\n *\n * @author 渔民小镇\n * @date 2024-12-16\n * @since 21.23\n */\n@ToString\n@ProtobufClass\n@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)\npublic enum AnimalTypeEnum implements EnumReadable {\n    /** the cat */\n    cat(0),\n    /** the tiger */\n    tiger(10),\n    ;\n\n    final int value;\n\n    AnimalTypeEnum(int value) {\n        this.value = value;\n    }\n\n    @Override\n    public int value() {\n        return this.value;\n    }\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/data/Cat.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf.data;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.widget.light.protobuf.ProtoFileMerge;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 猫\n *\n * @author 渔民小镇\n * @date 2022-01-25\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\n@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)\npublic class Cat {\n    /** id */\n    int id;\n    /** 猫的名字 */\n    String catName;\n    /** 食物 map */\n    Map<Integer, Integer> foodMap;\n    /** 道具 id 列表 */\n    List<Long> propIdList;\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/data/Food.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf.data;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.widget.light.protobuf.ProtoFileMerge;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 食物\n *\n * @author 渔民小镇\n * @date 2022-01-25\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\n@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)\npublic class Food {\n    /** id */\n    int id;\n    /** 食物名 */\n    String foodName;\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/data/ProtoTeacher.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf.data;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.widget.light.protobuf.ProtoFileMerge;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * teacher\n *\n * @author 渔民小镇\n * @date 2022-01-07\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\n@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)\npublic class ProtoTeacher {\n    /** 姓名 */\n    String name;\n    int id;\n    long age;\n    /** 邮箱 */\n    String email;\n\n    Double doubleF;\n    Float floatF;\n    byte[] bytesF;\n\n    Boolean boolF;\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/data/TempProtoFile.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf.data;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-25\n */\npublic interface TempProtoFile {\n    String fileName = \"common.proto\";\n    String filePackage = \"pb.common\";\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/data/TestEnum.java",
    "content": "package com.iohao.game.widget.light.protobuf.data;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.widget.light.protobuf.ProtoFileMerge;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 测试生成一个TestEnum\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\n@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)\npublic enum TestEnum {\n\n    /**\n     * AAAA\n     */\n    A,\n    /**\n     * BBBB\n     */\n    B,\n    ;\n}\n"
  },
  {
    "path": "widget/light-jprotobuf/src/test/java/com/iohao/game/widget/light/protobuf/data/Tiger.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.protobuf.data;\n\nimport com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\nimport com.iohao.game.widget.light.protobuf.ProtoFileMerge;\nimport lombok.AccessLevel;\nimport lombok.ToString;\nimport lombok.experimental.FieldDefaults;\n\n/**\n * 老虎\n *\n * @author 渔民小镇\n * @date 2022-01-25\n */\n@ToString\n@ProtobufClass\n@FieldDefaults(level = AccessLevel.PUBLIC)\n@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)\npublic class Tiger {\n    /** id */\n    int id;\n    /** 老虎的食物 */\n    Food food;\n}\n"
  },
  {
    "path": "widget/light-profile/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>ioGame</artifactId>\n        <groupId>com.iohao.game</groupId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>light-profile</artifactId>\n    <name>light-profile for ioGame</name>\n\n    <dependencies>\n        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-core</artifactId>\n            <version>${spring.version}</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "widget/light-profile/src/main/java/com/iohao/game/widget/light/profile/Profile.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.profile;\n\nimport lombok.ToString;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Properties;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Profile\n * <pre>\n *     配置文件中的的管理域\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Slf4j\n@ToString\npublic class Profile {\n    /** key */\n    String key;\n\n    Map<String, Object> map = new ConcurrentHashMap<>();\n\n    Profile() {\n    }\n\n    /**\n     * 获取 string 值\n     *\n     * @param key key\n     * @return 不存在返回 \"\" 空字符串\n     */\n    public String get(String key) {\n        return get(key, \"\");\n    }\n\n    /**\n     * 获取 string 值\n     *\n     * @param key    key\n     * @param defVal 默认值\n     * @return key 不存在, 返回默认值\n     */\n    public String get(String key, String defVal) {\n        Object value = map.get(key);\n        return Objects.isNull(value) ? defVal : value.toString();\n    }\n\n    /**\n     * 获取 bool 值\n     *\n     * @param key key\n     * @return key 不存在, 返回false\n     */\n    public boolean getBool(String key) {\n        return getBool(key, false);\n    }\n\n    /**\n     * 获取 bool 值\n     *\n     * @param key    key\n     * @param defVal 默认值\n     * @return key 不存在, 返回默认值\n     */\n    public boolean getBool(String key, boolean defVal) {\n        Object value = map.get(key);\n\n        try {\n            return Boolean.parseBoolean(value.toString());\n        } catch (Throwable e) {\n            return defVal;\n        }\n    }\n\n    /**\n     * 获取 int 值\n     *\n     * @param key key\n     * @return key 不存在, 返回0\n     */\n    public int getInt(String key) {\n        return getInt(key, 0);\n    }\n\n    /**\n     * 获取 int 值\n     *\n     * @param key    key\n     * @param defVal 默认值\n     * @return key 不存在, 返回默认值\n     */\n    public int getInt(String key, int defVal) {\n        Object value = map.get(key);\n\n        try {\n            return Integer.parseInt(value.toString());\n        } catch (Throwable e) {\n            return defVal;\n        }\n    }\n\n    /**\n     * 将 Properties 中的属性加载到当前对象中\n     *\n     * @param properties Properties\n     */\n    public void load(Properties properties) {\n        for (Object o : properties.keySet()) {\n            String key = o.toString();\n\n            Object value = properties.get(o);\n            // 理论上在这里做数据类型解析会好一些，但现在不着急\n\n            this.map.put(key, value);\n        }\n    }\n\n    /**\n     * 需要加载的配置文件\n     *\n     * @param urls 需要加载的配置文件\n     */\n    public void load(List<URL> urls) {\n        // 需要加载的配置文件\n        urls.forEach(url -> {\n\n            try (InputStream inputStream = url.openStream()) {\n                Properties properties = new Properties();\n                properties.load(inputStream);\n\n                this.load(properties);\n\n            } catch (IOException e) {\n                log.error(e.getMessage(), e);\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "widget/light-profile/src/main/java/com/iohao/game/widget/light/profile/ProfileManager.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.profile;\n\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang.StringUtils;\n\nimport java.net.URL;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\n\n/**\n * profile 配置与构建 <BR>\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Slf4j\n@UtilityClass\npublic final class ProfileManager {\n    /**\n     * 主配置key\n     */\n    final String MAIN_CONFIG = \"main_config\";\n    /**\n     * <pre>\n     *     key : profileName\n     *     value : profile\n     * </pre>\n     */\n    private final Map<String, Profile> profileMap = new ConcurrentHashMap<>();\n\n\n    public Profile profile() {\n        return profile(MAIN_CONFIG);\n    }\n\n    public Profile profile(final String key) {\n        Profile profile = profileMap.get(key);\n\n        // 无锁化\n        if (Objects.isNull(profile)) {\n            profile = new Profile();\n            profile.key = key;\n            profile = profileMap.putIfAbsent(key, profile);\n\n            if (Objects.isNull(profile)) {\n                profile = profileMap.get(key);\n            }\n        }\n\n        return profile;\n    }\n\n    /**\n     * 加载配置文件, /resources/conf下面的目录\n     * <pre>\n     * 数据格式以,分割.  例如: blocks,local (假设参数传入是这个字符串)\n     * 优先加载 blocks 目录下的配置文件.\n     * 然后加载 local 目录下的配置文件.\n     * 如果两个目录中有相同的配置项, 那么后面的会覆盖前面的配置项\n     *\n     * </pre>\n     *\n     * @param profileConfigName 数据格式以,分割. 例如: blocks,local\n     */\n    public void loadMainProfile(String profileConfigName) {\n        Optional<String> name = Optional.ofNullable(profileConfigName);\n\n        final String separator = \",\";\n        List<String> configNameList = Arrays.stream(name.orElse(\"\")\n                        .split(separator))\n                .filter(Objects::nonNull)\n                .map(String::trim)\n                .filter(s -> !s.isEmpty())\n                .collect(Collectors.toList());\n        log.debug(\"加载的目录列表 - size {} - {}\", configNameList.size(), configNameList);\n\n        // 加载配置文件\n        ResourcePatternResolverProfile resolverConfig = new ResourcePatternResolverProfile();\n        configNameList.forEach(resolverConfig::addDir);\n        List<URL> urlList = resolverConfig.toUrls();\n\n        // 将配置文件内容加载的 Profile 中\n        Profile profile = profile();\n        profile.load(urlList);\n\n        // 检查环境变量是否同名的参数 如果有则覆盖\n        profile.map.forEach((key, value) -> {\n            String envValue = System.getenv(key);\n            if (StringUtils.isNotEmpty(envValue)) {\n                profile.map.put(key, envValue);\n            }\n        });\n\n        log.debug(\"配置内容 - size:{} - {}\", ProfileManager.profile().map.size(), ProfileManager.profile());\n\n    }\n\n\n}\n"
  },
  {
    "path": "widget/light-profile/src/main/java/com/iohao/game/widget/light/profile/ResourcePatternResolverProfile.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.profile;\n\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.support.PathMatchingResourcePatternResolver;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Slf4j\nclass ResourcePatternResolverProfile {\n    /** 默认主目录 */\n    @Setter\n    private static String rootDir = \"conf\";\n    /** 默认加载主目录-子目录 */\n    @Setter\n    private static String defaultDir = \"common\";\n    /** 默认加载指定后缀文件 */\n    @Setter\n    private static String suffix = \".props\";\n\n    /** 需要加载的目录列表 */\n    private final LinkedList<String> dirNameList = new LinkedList<>();\n\n    /**\n     * 添加需要加载的目录\n     *\n     * @param dir 目录路径\n     */\n    public void addDir(String dir) {\n        dirNameList.add(dir);\n    }\n\n    /**\n     * 资源url\n     *\n     * @return 一定不为null\n     */\n    public List<URL> toUrls() {\n\n        // 优先加载 common 目录\n        dirNameList.addFirst(defaultDir);\n\n        List<URL> files = new LinkedList<>();\n\n        String locationPatternTemplate = \"classpath*:%s/%s/*%s\";\n        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();\n\n        try {\n            for (String dir : dirNameList) {\n                String locationPattern = String.format(locationPatternTemplate, rootDir, dir, suffix);\n\n                Resource[] resources = resolver.getResources(locationPattern);\n                log.debug(\"locationPattern: {}\", locationPattern);\n                for (Resource resource : resources) {\n                    URL url = resource.getURL();\n                    files.add(url);\n                }\n            }\n        } catch (IOException e) {\n            log.error(e.getMessage(), e);\n        }\n\n        return files;\n    }\n\n}\n"
  },
  {
    "path": "widget/light-profile/src/main/java/com/iohao/game/widget/light/profile/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 多环境配置\n * 无需修改代码实现（开发、测试、部署）配置间的切换\n *\n * <pre>\n *     此模块目前暂时依赖 spring-core 介意的先不要使用\n *     后面有时间在去 spring 化\n *     主要使用的 spring 的资源读取，所以影响并不是很大\n * </pre>\n *\n * @author 渔民小镇\n * @date 2022-01-02\n */\npackage com.iohao.game.widget.light.profile;"
  },
  {
    "path": "widget/light-profile/src/test/java/com/iohao/game/widget/light/profile/ProfileManagerTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.profile;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Test;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Slf4j\npublic class ProfileManagerTest {\n\n    @Test\n    public void profile() {\n        // 加载环境配置\n        String profileConfigName = \"local\";\n        ProfileManager.loadMainProfile(profileConfigName);\n\n        Profile profile = ProfileManager.profile();\n        // 调用配置文件中的配置\n        String jdbcDriver = profile.get(\"jdbcDriver\");\n        boolean devMode = profile.getBool(\"devMode\");\n        int maxVip = profile.getInt(\"maxVip\");\n\n        int externalPort = profile.getInt(\"external.port\");\n\n        System.out.println();\n\n        log.info(\"jdbcDriver : {}\", jdbcDriver);\n        log.info(\"devMode : {}\", devMode);\n        log.info(\"maxVip : {}\", maxVip);\n        log.info(\"externalPort : {}\", externalPort);\n    }\n}"
  },
  {
    "path": "widget/light-profile/src/test/java/com/iohao/game/widget/light/profile/ResourcePatternResolverProfileTest.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage com.iohao.game.widget.light.profile;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.Test;\n\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * @author 渔民小镇\n * @date 2022-01-02\n */\n@Slf4j\npublic class ResourcePatternResolverProfileTest {\n    @Test\n    public void loadLocal() {\n        ResourcePatternResolverProfile config = new ResourcePatternResolverProfile();\n        // 加载 local 目录\n        config.addDir(\"local\");\n\n        List<URL> urls = config.toUrls();\n        log.info(\"local profile {}\", urls.size());\n\n\n    }\n}"
  },
  {
    "path": "widget/light-profile/src/test/resources/conf/common/db.props",
    "content": "jdbcDriver = com.mysql.jdbc.Driver\njdbcUrl = jdbc:mysql://xxx:3306/myarpg?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull\njdbcUser = common\njdbcPwd = commonPwd\n"
  },
  {
    "path": "widget/light-profile/src/test/resources/conf/common/other.props",
    "content": "# 是否启动开发模式， true：开发模式。\ndevMode = true\n\nmaxVip = 7\n\nexternal.port = 10100"
  },
  {
    "path": "widget/light-profile/src/test/resources/conf/local/db.props",
    "content": "jdbcUser = local\njdbcPwd = localPwd"
  },
  {
    "path": "widget/light-profile/src/test/resources/conf/production/db.props",
    "content": "jdbcUser = production\njdbcPwd = productionPwd\n"
  },
  {
    "path": "widget/light-profile/src/test/resources/conf/production/other.props",
    "content": "# 是否启动开发模式， true：开发模式。\ndevMode = false\n"
  },
  {
    "path": "widget/other-tool/README.md",
    "content": "### 第三方工具\n\n此模块是 copy 自 https://gitee.com/dromara/hutool\n\n由于只使用了部分功能，就不引入 hutool 的包了，目的是缩小包体。\n\nLICENSE\nhttps://gitee.com/dromara/hutool/blob/v5-master/LICENSE\n\n\n\n这个模块中文件内的 author 仅表示添加人。\n\n这个模块中只添加了 hutool 中小部分的 File 和 StrUtil.format\n\n\n\n```java\nopen module com.iohao.game.common.kit.other.tool {\n    requires transitive lombok;\n\n    exports com.iohao.game.common.kit.hutool;\n}\n```"
  },
  {
    "path": "widget/other-tool/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>com.iohao.game</groupId>\n        <artifactId>ioGame</artifactId>\n        <version>21.34</version>\n        <relativePath>../../pom.xml</relativePath>\n    </parent>\n\n    <artifactId>other-tool</artifactId>\n    <name>other-tool for ioGame</name>\n</project>"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/AdapterHuUtils.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport lombok.NonNull;\nimport lombok.experimental.UtilityClass;\n\nimport java.io.File;\nimport java.nio.charset.Charset;\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n@UtilityClass\npublic class AdapterHuUtils {\n    public String readStr(String resource, Charset charset) {\n        return HuResourceUtil.readStr(resource, charset);\n    }\n\n    public File mkdir(String dirPath) {\n        return HuFileUtil.mkdir(dirPath);\n    }\n\n    public File file(String path) {\n        return HuFileUtil.file(path);\n    }\n\n    public File writeUtf8String(String content, String path) {\n        return HuFileUtil.writeUtf8String(content, path);\n    }\n\n    public boolean isDirectory(String path) {\n        return HuFileUtil.isDirectory(path);\n    }\n\n    public static boolean exist(File file) {\n        return (null != file) && file.exists();\n    }\n\n    public String format(@NonNull CharSequence template, @NonNull Map<?, ?> map) {\n        return HuStrUtil.format(template, map);\n    }\n\n    public String format(@NonNull CharSequence template, Object... params) {\n        return HuStrUtil.format(template, params);\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuArrayUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.util.Arrays;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuArrayUtil {\n    public static final int INDEX_NOT_FOUND = -1;\n\n    public static <T> boolean isEmpty(T[] array) {\n        return array == null || array.length == 0;\n    }\n\n\n    public static boolean isArray(Object obj) {\n        return null != obj && obj.getClass().isArray();\n    }\n\n    public static String toString(Object obj) {\n        if (null == obj) {\n            return null;\n        }\n\n        if (obj instanceof long[] value) {\n            return Arrays.toString(value);\n        } else if (obj instanceof int[] value) {\n            return Arrays.toString(value);\n        } else if (obj instanceof short[] value) {\n            return Arrays.toString(value);\n        } else if (obj instanceof char[] value) {\n            return Arrays.toString(value);\n        } else if (obj instanceof byte[] value) {\n            return Arrays.toString(value);\n        } else if (obj instanceof boolean[] value) {\n            return Arrays.toString(value);\n        } else if (obj instanceof float[] value) {\n            return Arrays.toString(value);\n        } else if (obj instanceof double[] value) {\n            return Arrays.toString(value);\n        } else if (HuArrayUtil.isArray(obj)) {\n            // 对象数组\n            try {\n                return Arrays.deepToString((Object[]) obj);\n            } catch (Exception ignore) {\n                //ignore\n            }\n        }\n\n        return obj.toString();\n    }\n\n    public static boolean contains(char[] array, char value) {\n        return indexOf(array, value) > INDEX_NOT_FOUND;\n    }\n\n    public static int indexOf(char[] array, char value) {\n        if (null != array) {\n            for (int i = 0; i < array.length; i++) {\n                if (value == array[i]) {\n                    return i;\n                }\n            }\n        }\n        return INDEX_NOT_FOUND;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuAssert.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.util.function.Supplier;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n\nclass HuAssert {\n    public static <T> T notNull(T object, String errorMsgTemplate, Object... params) throws IllegalArgumentException {\n        return notNull(object, () -> new IllegalArgumentException(HuStrUtil.format(errorMsgTemplate, params)));\n    }\n\n    public static <T extends CharSequence> T notEmpty(T text) throws IllegalArgumentException {\n        return notEmpty(text, \"[Assertion failed] - this String argument must have length; it must not be null or empty\");\n    }\n\n    public static <T extends CharSequence> T notEmpty(T text, String errorMsgTemplate, Object... params) throws IllegalArgumentException {\n        return notEmpty(text, () -> new IllegalArgumentException(HuStrUtil.format(errorMsgTemplate, params)));\n    }\n\n    public static <T extends CharSequence, X extends Throwable> T notEmpty(T text, Supplier<X> errorSupplier) throws X {\n        if (HuStrUtil.isEmpty(text)) {\n            throw errorSupplier.get();\n        }\n        return text;\n    }\n\n    public static <T, X extends Throwable> T notNull(T object, Supplier<X> errorSupplier) throws X {\n        if (null == object) {\n            throw errorSupplier.get();\n        }\n        return object;\n    }\n\n    public static void isFalse(boolean expression, String errorMsgTemplate, Object... params) throws IllegalArgumentException {\n        isFalse(expression, () -> new IllegalArgumentException(HuStrUtil.format(errorMsgTemplate, params)));\n    }\n\n    public static <X extends Throwable> void isFalse(boolean expression, Supplier<X> errorSupplier) throws X {\n        if (expression) {\n            throw errorSupplier.get();\n        }\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuBase16Codec.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuBase16Codec {\n\n    private final char[] alphabets;\n\n    public HuBase16Codec(boolean lowerCase) {\n        this.alphabets = (lowerCase ? \"0123456789abcdef\" : \"0123456789ABCDEF\").toCharArray();\n    }\n\n    public char[] encode(byte[] data) {\n        final int len = data.length;\n        final char[] out = new char[len << 1];\n        //len*2\n        // two characters from the hex value.\n        for (int i = 0, j = 0; i < len; i++) {\n            out[j++] = alphabets[(0xF0 & data[i]) >>> 4];\n            // 高位\n            out[j++] = alphabets[0x0F & data[i]];\n            // 低位\n        }\n        return out;\n    }\n\n    public byte[] decode(CharSequence encoded) {\n        if (HuStrUtil.isEmpty(encoded)) {\n            return new byte[0];\n        }\n\n        encoded = HuStrUtil.cleanBlank(encoded);\n        int len = encoded.length();\n\n        if ((len & 0x01) != 0) {\n            // 如果提供的数据是奇数长度，则前面补0凑偶数\n            encoded = \"0\" + encoded;\n            len = encoded.length();\n        }\n\n        final byte[] out = new byte[len >> 1];\n\n        // two characters form the hex value.\n        for (int i = 0, j = 0; j < len; i++) {\n            int f = toDigit(encoded.charAt(j), j) << 4;\n            j++;\n            f = f | toDigit(encoded.charAt(j), j);\n            j++;\n            out[i] = (byte) (f & 0xFF);\n        }\n\n        return out;\n    }\n\n\n    public void appendHex(StringBuilder builder, byte b) {\n        int high = (b & 0xf0) >>> 4;\n        int low = b & 0x0f;\n        builder.append(alphabets[high]);\n        builder.append(alphabets[low]);\n    }\n\n    private static int toDigit(char ch, int index) {\n        int digit = Character.digit(ch, 16);\n        if (digit < 0) {\n            throw new HuUtilException(\"Illegal hexadecimal character {} at index {}\", ch, index);\n        }\n        return digit;\n    }\n}"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuCharFinder.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.Serial;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuCharFinder extends HuTextFinder {\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    private final char c;\n    private final boolean caseInsensitive;\n\n    /**\n     * 构造，不忽略字符大小写\n     *\n     * @param c 被查找的字符\n     */\n    public HuCharFinder(char c) {\n        this(c, false);\n    }\n\n    /**\n     * 构造\n     *\n     * @param c               被查找的字符\n     * @param caseInsensitive 是否忽略大小写\n     */\n    public HuCharFinder(char c, boolean caseInsensitive) {\n        this.c = c;\n        this.caseInsensitive = caseInsensitive;\n    }\n\n    @Override\n    public int start(int from) {\n        HuAssert.notNull(this.text, \"Text to find must be not null!\");\n        final int limit = getValidEndIndex();\n        if (negative) {\n            for (int i = from; i > limit; i--) {\n                if (HuCharUtil.equals(c, text.charAt(i), caseInsensitive)) {\n                    return i;\n                }\n            }\n        } else {\n            for (int i = from; i < limit; i++) {\n                if (HuCharUtil.equals(c, text.charAt(i), caseInsensitive)) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    }\n\n    @Override\n    public int end(int start) {\n        if (start < 0) {\n            return -1;\n        }\n        return start + 1;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuCharUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n class HuCharUtil {\n\n    public static final char SPACE = ' ';\n\n    public static final char SLASH = '/';\n\n    public static final char BACKSLASH = '\\\\';\n\n    public static boolean isBlankChar(char c) {\n        return isBlankChar((int) c);\n    }\n\n    public static boolean equals(char c1, char c2, boolean caseInsensitive) {\n        if (caseInsensitive) {\n            return Character.toLowerCase(c1) == Character.toLowerCase(c2);\n        }\n        return c1 == c2;\n    }\n\n    public static boolean isBlankChar(int c) {\n        return Character.isWhitespace(c)\n                || Character.isSpaceChar(c)\n                || c == '\\ufeff'\n                || c == '\\u202a'\n                || c == '\\u0000'\n                // issue#I5UGSQ，Hangul Filler\n                || c == '\\u3164'\n                // Braille Pattern Blank\n                || c == '\\u2800'\n                // MONGOLIAN VOWEL SEPARATOR\n                || c == '\\u180e';\n    }\n\n    public static boolean isFileSeparator(char c) {\n        return SLASH == c || BACKSLASH == c;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuCharsetUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.charset.UnsupportedCharsetException;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuCharsetUtil {\n\n    public static final Charset CHARSET_UTF_8 = StandardCharsets.UTF_8;\n\n    public static Charset charset(String charsetName) throws UnsupportedCharsetException {\n        return HuStrUtil.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName);\n    }\n\n    public static Charset defaultCharset() {\n        return Charset.defaultCharset();\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuClassLoaderUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuClassLoaderUtil {\n\n    public static ClassLoader getClassLoader() {\n        ClassLoader classLoader = getContextClassLoader();\n        if (classLoader == null) {\n            classLoader = HuClassLoaderUtil.class.getClassLoader();\n            if (null == classLoader) {\n                classLoader = getSystemClassLoader();\n            }\n        }\n\n        return classLoader;\n    }\n\n    public static ClassLoader getSystemClassLoader() {\n        return ClassLoader.getSystemClassLoader();\n    }\n\n    public static ClassLoader getContextClassLoader() {\n        return Thread.currentThread().getContextClassLoader();\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuClassPathResource.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.io.Serial;\nimport java.net.URL;\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuClassPathResource extends HuUrlResource {\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    private final String path;\n    private final ClassLoader classLoader;\n    private final Class<?> clazz;\n    static final String SLASH = \"/\";\n\n    public HuClassPathResource(String path) {\n        this(path, null, null);\n    }\n\n    public HuClassPathResource(String pathBaseClassLoader, ClassLoader classLoader, Class<?> clazz) {\n        super((URL) null);\n\n        Objects.requireNonNull(pathBaseClassLoader);\n\n        final String path = normalizePath(pathBaseClassLoader);\n        this.path = path;\n        this.name = HuStrUtil.isBlank(path) ? null : HuFileUtil.getName(path);\n\n        this.classLoader = defaultIfNull(classLoader, HuClassUtil::getClassLoader);\n        this.clazz = clazz;\n        initUrl();\n    }\n\n    public static <T> T defaultIfNull(T source, Supplier<? extends T> defaultValueSupplier) {\n        if (Objects.isNull(source)) {\n            return defaultValueSupplier.get();\n        }\n        return source;\n    }\n\n    public final String getPath() {\n        return this.path;\n    }\n\n    private void initUrl() {\n        if (null != this.clazz) {\n            super.url = this.clazz.getResource(this.path);\n        } else if (null != this.classLoader) {\n            super.url = this.classLoader.getResource(this.path);\n        } else {\n            super.url = ClassLoader.getSystemResource(this.path);\n        }\n        if (null == super.url) {\n            throw new HuNoResourceException(\"Resource of path [{}] not exist!\", this.path);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return (null == this.path) ? super.toString() : \"classpath:\" + this.path;\n    }\n\n    private String normalizePath(String path) {\n        // 标准化路径\n        path = HuFileUtil.normalize(path);\n        path = HuStrUtil.removePrefix(path, SLASH);\n\n        HuAssert.isFalse(HuFileUtil.isAbsolutePath(path), \"Path [{}] must be a relative path !\", path);\n        return path;\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuClassUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.net.URL;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n class HuClassUtil {\n\n    public static String getClassPath() {\n        final URL classPathURL = HuResourceUtil.getResource(\"\");\n        String url = HuUrlUtil.getDecodedPath(classPathURL);\n        return HuFileUtil.normalize(url);\n    }\n\n    public static ClassLoader getClassLoader() {\n        return HuClassLoaderUtil.getClassLoader();\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuComputeIter.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n abstract class HuComputeIter<T> implements Iterator<T> {\n\n    private T next;\n    private boolean finished;\n\n    protected abstract T computeNext();\n\n    @Override\n    public boolean hasNext() {\n        if (null != next) {\n            // 用户读取了节点，但是没有使用\n            return true;\n        } else if (finished) {\n            // 读取结束\n            return false;\n        }\n\n        T result = computeNext();\n        if (null == result) {\n            // 不再有新的节点，结束\n            this.finished = true;\n            return false;\n        } else {\n            this.next = result;\n            return true;\n        }\n\n    }\n\n    @Override\n    public T next() {\n        if (!hasNext()) {\n            throw new NoSuchElementException(\"No more lines\");\n        }\n\n        T result = this.next;\n        // 清空cache，表示此节点读取完毕，下次计算新节点\n        this.next = null;\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuCopyVisitor.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.nio.file.attribute.BasicFileAttributes;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuCopyVisitor extends SimpleFileVisitor<Path> {\n\n    private final Path source;\n    private final Path target;\n    private final CopyOption[] copyOptions;\n    private boolean isTargetCreated;\n\n    public HuCopyVisitor(Path source, Path target, CopyOption... copyOptions) {\n        if (HuFileUtil.exists(target, false) && !HuFileUtil.isDirectory(target)) {\n            throw new IllegalArgumentException(\"Target must be a directory\");\n        }\n        this.source = source;\n        this.target = target;\n        this.copyOptions = copyOptions;\n    }\n\n    @Override\n    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\n        initTargetDir();\n        // 将当前目录相对于源路径转换为相对于目标路径\n        final Path targetDir = resolveTarget(dir);\n\n        // 在目录不存在的情况下，copy方法会创建新目录\n        try {\n            Files.copy(dir, targetDir, copyOptions);\n        } catch (FileAlreadyExistsException e) {\n            if (!Files.isDirectory(targetDir)) {\n                // 目标文件存在抛出异常，目录忽略\n                throw e;\n            }\n        }\n        return FileVisitResult.CONTINUE;\n    }\n\n    @Override\n    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)\n            throws IOException {\n        initTargetDir();\n        // 如果目标存在，无论目录还是文件都抛出FileAlreadyExistsException异常，此处不做特别处理\n        Files.copy(file, resolveTarget(file), copyOptions);\n        return FileVisitResult.CONTINUE;\n    }\n\n    private Path resolveTarget(Path file) {\n        return target.resolve(source.relativize(file));\n    }\n\n    private void initTargetDir() {\n        if (!this.isTargetCreated) {\n            HuFileUtil.mkdir(this.target);\n            this.isTargetCreated = true;\n        }\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuExceptionUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuExceptionUtil {\n\n    public static String getMessage(Throwable e) {\n        if (null == e) {\n            return HuStrUtil.NULL;\n        }\n\n        return HuStrUtil.format(\"{}: {}\", e.getClass().getSimpleName(), e.getMessage());\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuFastByteArrayOutputStream.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.OutputStream;\nimport java.nio.charset.Charset;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuFastByteArrayOutputStream extends OutputStream {\n\n    private final HuFastByteBuffer buffer;\n\n    public HuFastByteArrayOutputStream() {\n        this(1024);\n    }\n\n    public HuFastByteArrayOutputStream(int size) {\n        buffer = new HuFastByteBuffer(size);\n    }\n\n    @Override\n    public void write(byte[] b, int off, int len) {\n        buffer.append(b, off, len);\n    }\n\n    @Override\n    public void write(int b) {\n        buffer.append((byte) b);\n    }\n\n    public int size() {\n        return buffer.size();\n    }\n\n    @Override\n    public void close() {\n    }\n\n    public void reset() {\n        buffer.reset();\n    }\n\n\n    public byte[] toByteArray() {\n        return buffer.toArray();\n    }\n\n    @Override\n    public String toString() {\n        return toString(HuCharsetUtil.defaultCharset());\n    }\n\n    public String toString(String charsetName) {\n        return toString(HuCharsetUtil.charset(charsetName));\n    }\n\n    public String toString(Charset charset) {\n        return new String(toByteArray(),\n                HuObjectUtil.defaultIfNull(charset, HuCharsetUtil.defaultCharset()));\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuFastByteBuffer.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuFastByteBuffer {\n\n    private byte[][] buffers = new byte[16][];\n\n    private int currentBufferIndex = -1;\n    private byte[] currentBuffer;\n    private int offset;\n    private int size;\n\n    private final int minChunkLen;\n\n\n    public HuFastByteBuffer(int size) {\n        if (size <= 0) {\n            size = 1024;\n        }\n        this.minChunkLen = Math.abs(size);\n    }\n\n    private void needNewBuffer(int newSize) {\n        int delta = newSize - size;\n        int newBufferSize = Math.max(minChunkLen, delta);\n\n        currentBufferIndex++;\n        currentBuffer = new byte[newBufferSize];\n        offset = 0;\n\n        // add buffer\n        if (currentBufferIndex >= buffers.length) {\n            int newLen = buffers.length << 1;\n            byte[][] newBuffers = new byte[newLen][];\n            System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);\n            buffers = newBuffers;\n        }\n        buffers[currentBufferIndex] = currentBuffer;\n    }\n\n    public HuFastByteBuffer append(byte[] array, int off, int len) {\n        int end = off + len;\n        if ((off < 0) || (len < 0) || (end > array.length)) {\n            throw new IndexOutOfBoundsException();\n        }\n        if (len == 0) {\n            return this;\n        }\n        int newSize = size + len;\n        int remaining = len;\n\n        if (currentBuffer != null) {\n            // first try to fill current buffer\n            int part = Math.min(remaining, currentBuffer.length - offset);\n            System.arraycopy(array, end - remaining, currentBuffer, offset, part);\n            remaining -= part;\n            offset += part;\n            size += part;\n        }\n\n        if (remaining > 0) {\n            // still some data left\n            // ask for new buffer\n            needNewBuffer(newSize);\n\n            // then copy remaining\n            // but this time we are sure that it will fit\n            int part = Math.min(remaining, currentBuffer.length - offset);\n            System.arraycopy(array, end - remaining, currentBuffer, offset, part);\n            offset += part;\n            size += part;\n        }\n\n        return this;\n    }\n\n    public HuFastByteBuffer append(byte[] array) {\n        return append(array, 0, array.length);\n    }\n\n    public HuFastByteBuffer append(byte element) {\n        if ((currentBuffer == null) || (offset == currentBuffer.length)) {\n            needNewBuffer(size + 1);\n        }\n\n        currentBuffer[offset] = element;\n        offset++;\n        size++;\n\n        return this;\n    }\n\n    public HuFastByteBuffer append(HuFastByteBuffer buff) {\n        if (buff.size == 0) {\n            return this;\n        }\n        for (int i = 0; i < buff.currentBufferIndex; i++) {\n            append(buff.buffers[i]);\n        }\n        append(buff.currentBuffer, 0, buff.offset);\n        return this;\n    }\n\n    public int size() {\n        return size;\n    }\n\n    public boolean isEmpty() {\n        return size == 0;\n    }\n\n    public int index() {\n        return currentBufferIndex;\n    }\n\n    public int offset() {\n        return offset;\n    }\n\n    public byte[] array(int index) {\n        return buffers[index];\n    }\n\n    public void reset() {\n        size = 0;\n        offset = 0;\n        currentBufferIndex = -1;\n        currentBuffer = null;\n    }\n\n    public byte[] toArray() {\n        int pos = 0;\n        byte[] array = new byte[size];\n\n        if (currentBufferIndex == -1) {\n            return array;\n        }\n\n        for (int i = 0; i < currentBufferIndex; i++) {\n            int len = buffers[i].length;\n            System.arraycopy(buffers[i], 0, array, pos, len);\n            pos += len;\n        }\n\n        System.arraycopy(buffers[currentBufferIndex], 0, array, pos, offset);\n\n        return array;\n    }\n\n    public byte get(int index) {\n        if ((index >= size) || (index < 0)) {\n            throw new IndexOutOfBoundsException();\n        }\n        int ndx = 0;\n        while (true) {\n            byte[] b = buffers[ndx];\n            if (index < b.length) {\n                return b[index];\n            }\n            ndx++;\n            index -= b.length;\n        }\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuFileResource.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.io.Serializable;\nimport java.net.URL;\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuFileResource implements HuResource, Serializable {\n\n    private final File file;\n    private final String name;\n\n    public HuFileResource(String path) {\n        this(HuFileUtil.file(path));\n    }\n\n    public HuFileResource(File file) {\n        this(file, null);\n    }\n\n    public HuFileResource(File file, String fileName) {\n        Objects.requireNonNull(file);\n        Objects.requireNonNull(file);\n        this.file = file;\n        this.name = defaultIfNull(fileName, file::getName);\n    }\n\n    private static <T> T defaultIfNull(T source, Supplier<? extends T> defaultValueSupplier) {\n        if (Objects.isNull(source)) {\n            return defaultValueSupplier.get();\n        }\n        return source;\n    }\n\n    @Override\n    public String getName() {\n        return this.name;\n    }\n\n    @Override\n    public URL getUrl() {\n        return HuUrlUtil.getURL(this.file);\n    }\n\n    @Override\n    public InputStream getStream() throws HuNoResourceException {\n        return HuFileUtil.getInputStream(this.file);\n    }\n\n    public File getFile() {\n        return this.file;\n    }\n\n    @Override\n    public String toString() {\n        return this.file.toString();\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuFileUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.*;\nimport java.net.URL;\nimport java.nio.charset.Charset;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Pattern;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuFileUtil extends HuPathUtil {\n\n    private static final Pattern PATTERN_PATH_ABSOLUTE = Pattern.compile(\"^[a-zA-Z]:([/\\\\\\\\].*)?\");\n\n    public static String getName(String filePath) {\n\n        if (null == filePath) {\n            return null;\n        }\n        int len = filePath.length();\n        if (0 == len) {\n            return filePath;\n        }\n        if (HuCharUtil.isFileSeparator(filePath.charAt(len - 1))) {\n            // 以分隔符结尾的去掉结尾分隔符\n            len--;\n        }\n\n        int begin = 0;\n        char c;\n        for (int i = len - 1; i > -1; i--) {\n            c = filePath.charAt(i);\n            if (HuCharUtil.isFileSeparator(c)) {\n                // 查找最后一个路径分隔符（/或者\\）\n                begin = i + 1;\n                break;\n            }\n        }\n\n        return filePath.substring(begin, len);\n    }\n\n    public static File file(URL url) {\n        return new File(HuUrlUtil.toURI(url));\n    }\n\n    public static BufferedInputStream getInputStream(File file) throws HuIoRuntimeException {\n        return HuIoUtil.toBuffered(HuIoUtil.toStream(file));\n    }\n\n    public static File mkdir(String dirPath) {\n        if (dirPath == null) {\n            return null;\n        }\n        final File dir = file(dirPath);\n        return mkdir(dir);\n    }\n\n\n    public static File mkdir(File dir) {\n        if (dir == null) {\n            return null;\n        }\n        if (!dir.exists()) {\n            mkdirSafely(dir, 5, 1);\n        }\n        return dir;\n    }\n\n    public static boolean mkdirSafely(File dir, int tryCount, long sleepMillis) {\n        if (dir == null) {\n            return false;\n        }\n\n        if (dir.isDirectory()) {\n            return true;\n        }\n\n        for (int i = 1; i <= tryCount; i++) {\n            // 高并发场景下，可以看到 i 处于 1 ~ 3 之间\n            // 如果文件已存在，也会返回 false，所以该值不能作为是否能创建的依据，因此不对其进行处理\n            //noinspection ResultOfMethodCallIgnored\n            dir.mkdirs();\n            if (dir.exists()) {\n                return true;\n            }\n\n            if (sleepMillis > 0) {\n                try {\n                    TimeUnit.MILLISECONDS.sleep(sleepMillis);\n                } catch (InterruptedException e) {\n                    throw new RuntimeException(e);\n                }\n            }\n\n        }\n        return dir.exists();\n    }\n\n    public static File file(String path) {\n        if (null == path) {\n            return null;\n        }\n\n        return new File(getAbsolutePath(path));\n    }\n\n\n    public static String getAbsolutePath(String path) {\n        return getAbsolutePath(path, null);\n    }\n\n\n    public static String getAbsolutePath(String path, Class<?> baseClass) {\n        String normalPath;\n        if (path == null) {\n            normalPath = \"\";\n        } else {\n            normalPath = normalize(path);\n            if (isAbsolutePath(normalPath)) {\n                // 给定的路径已经是绝对路径了\n                return normalPath;\n            }\n        }\n\n        // 相对于ClassPath路径\n        final URL url = HuResourceUtil.getResource(normalPath, baseClass);\n        if (null != url) {\n            // 对于jar中文件包含file:前缀，需要去掉此类前缀，在此做标准化，since 3.0.8 解决中文或空格路径被编码的问题\n            return HuFileUtil.normalize(HuUrlUtil.getDecodedPath(url));\n        }\n\n        // 如果资源不存在，则返回一个拼接的资源绝对路径\n        final String classPath = HuClassUtil.getClassPath();\n        if (null == classPath) {\n            // 在jar运行模式中，ClassPath有可能获取不到，此时返回原始相对路径（此时获取的文件为相对工作目录）\n            return path;\n        }\n\n        // 资源不存在的情况下使用标准化路径有问题，使用原始路径拼接后标准化路径\n        return normalize(classPath.concat(Objects.requireNonNull(path)));\n    }\n\n\n    public static String normalize(String path) {\n        if (path == null) {\n            return null;\n        }\n\n        // 兼容Spring风格的ClassPath路径，去除前缀，不区分大小写\n        String pathToUse = HuStrUtil.removePrefixIgnoreCase(path, HuUrlUtil.CLASSPATH_URL_PREFIX);\n        // 去除file:前缀\n        pathToUse = HuStrUtil.removePrefixIgnoreCase(pathToUse, HuUrlUtil.FILE_URL_PREFIX);\n\n        // 识别home目录形式，并转换为绝对路径\n        if (HuStrUtil.startWith(pathToUse, '~')) {\n            pathToUse = getUserHomePath() + pathToUse.substring(1);\n        }\n\n        // 统一使用斜杠\n        pathToUse = pathToUse.replaceAll(\"[/\\\\\\\\]+\", HuStrUtil.SLASH);\n        // 去除开头空白符，末尾空白符合法，不去除\n        pathToUse = HuStrUtil.trimStart(pathToUse);\n        //兼容Windows下的共享目录路径（原始路径如果以\\\\开头，则保留这种路径）\n        if (path.startsWith(\"\\\\\\\\\")) {\n            pathToUse = \"\\\\\" + pathToUse;\n        }\n\n        String prefix = HuStrUtil.EMPTY;\n        int prefixIndex = pathToUse.indexOf(HuStrUtil.COLON);\n        if (prefixIndex > -1) {\n            // 可能Windows风格路径\n            prefix = pathToUse.substring(0, prefixIndex + 1);\n            if (HuStrUtil.startWith(prefix, HuStrUtil.C_SLASH)) {\n                // 去除类似于/C:这类路径开头的斜杠\n                prefix = prefix.substring(1);\n            }\n            if (!prefix.contains(HuStrUtil.SLASH)) {\n                pathToUse = pathToUse.substring(prefixIndex + 1);\n            } else {\n                // 如果前缀中包含/,说明非Windows风格path\n                prefix = HuStrUtil.EMPTY;\n            }\n        }\n        if (pathToUse.startsWith(HuStrUtil.SLASH)) {\n            prefix += HuStrUtil.SLASH;\n            pathToUse = pathToUse.substring(1);\n        }\n\n        List<String> pathList = HuStrUtil.split(pathToUse, HuStrUtil.C_SLASH);\n\n        List<String> pathElements = new LinkedList<>();\n        int tops = 0;\n        String element;\n        for (int i = pathList.size() - 1; i >= 0; i--) {\n            element = pathList.get(i);\n            // 只处理非.的目录，即只处理非当前目录\n            if (!HuStrUtil.DOT.equals(element)) {\n                if (HuStrUtil.DOUBLE_DOT.equals(element)) {\n                    tops++;\n                } else {\n                    if (tops > 0) {\n                        // 有上级目录标记时按照个数依次跳过\n                        tops--;\n                    } else {\n                        // Normal path element found.\n                        pathElements.add(0, element);\n                    }\n                }\n            }\n        }\n\n        // issue#1703@Github\n        if (tops > 0 && HuStrUtil.isEmpty(prefix)) {\n            // 只有相对路径补充开头的..，绝对路径直接忽略之\n            while (tops-- > 0) {\n                //遍历完节点发现还有上级标注（即开头有一个或多个..），补充之\n                // Normal path element found.\n                pathElements.add(0, HuStrUtil.DOUBLE_DOT);\n            }\n        }\n\n\n        return prefix + String.join(HuStrUtil.SLASH, pathElements);\n    }\n\n    /**\n     * 获取用户路径（绝对路径）\n     *\n     * @return 用户路径\n     * @since 4.0.6\n     */\n    public static String getUserHomePath() {\n        return System.getProperty(\"user.home\");\n    }\n\n    public static boolean isAbsolutePath(String path) {\n        if (HuStrUtil.isEmpty(path)) {\n            return false;\n        }\n\n        // 给定的路径已经是绝对路径了\n        return HuStrUtil.C_SLASH == path.charAt(0) || isMatch(PATTERN_PATH_ABSOLUTE, path);\n    }\n\n    private static boolean isMatch(Pattern pattern, CharSequence content) {\n        if (content == null || pattern == null) {\n            // 提供null的字符串为不匹配\n            return false;\n        }\n        return pattern.matcher(content).matches();\n    }\n\n    public static File writeUtf8String(String content, String path) throws HuIoRuntimeException {\n        return writeString(content, path, HuCharsetUtil.CHARSET_UTF_8);\n    }\n\n    public static File writeString(String content, String path, Charset charset) throws HuIoRuntimeException {\n        return writeString(content, touch(path), charset);\n    }\n\n    public static File writeString(String content, File file, Charset charset) throws HuIoRuntimeException {\n        return HuFileWriter.create(file, charset).write(content);\n    }\n\n    public static File touch(String path) throws HuIoRuntimeException {\n        if (path == null) {\n            return null;\n        }\n        return touch(file(path));\n    }\n\n    public static boolean isDirectory(String path) {\n        return (null != path) && file(path).isDirectory();\n    }\n\n    public static File touch(File file) throws HuIoRuntimeException {\n        if (null == file) {\n            return null;\n        }\n\n        if (!file.exists()) {\n            mkParentDirs(file);\n            try {\n                //noinspection ResultOfMethodCallIgnored\n                file.createNewFile();\n            } catch (Exception e) {\n                throw new HuIoRuntimeException(e);\n            }\n        }\n        return file;\n    }\n\n    public static File mkParentDirs(File file) {\n        if (null == file) {\n            return null;\n        }\n        return mkdir(getParent(file, 1));\n    }\n\n    public static File getParent(File file, int level) {\n        if (level < 1 || null == file) {\n            return file;\n        }\n\n        File parentFile;\n        try {\n            parentFile = file.getCanonicalFile().getParentFile();\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        }\n        if (1 == level) {\n            return parentFile;\n        }\n        return getParent(parentFile, level - 1);\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuFileWriter.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.*;\nimport java.nio.charset.Charset;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n class HuFileWriter implements Serializable {\n\n    protected File file;\n    protected Charset charset;\n\n    public static HuFileWriter create(File file, Charset charset) {\n        return new HuFileWriter(file, charset);\n    }\n\n    public HuFileWriter(File file, Charset charset) {\n        this.file = file;\n        this.charset = charset;\n        checkFile();\n    }\n\n    private void checkFile() throws HuIoRuntimeException {\n        HuAssert.notNull(file, \"File to write content is null !\");\n        if (this.file.exists() && !file.isFile()) {\n            throw new HuIoRuntimeException(\"File [{}] is not a file !\", this.file.getAbsoluteFile());\n        }\n    }\n\n    public File write(String content) throws HuIoRuntimeException {\n        return write(content, false);\n    }\n\n    public File write(String content, boolean isAppend) throws HuIoRuntimeException {\n        BufferedWriter writer = null;\n        try {\n            writer = getWriter(isAppend);\n            writer.write(content);\n            writer.flush();\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        } finally {\n            HuIoUtil.close(writer);\n        }\n        return file;\n    }\n\n    public BufferedWriter getWriter(boolean isAppend) throws HuIoRuntimeException {\n        try {\n            return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(HuFileUtil.touch(file), isAppend), charset));\n        } catch (Exception e) {\n            throw new HuIoRuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuFilter.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n@FunctionalInterface\n interface HuFilter<T> {\n    /**\n     * 是否接受对象\n     *\n     * @param t 检查的对象\n     * @return 是否接受对象\n     */\n    boolean accept(T t);\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuFinder.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\ninterface HuFinder {\n\n    int INDEX_NOT_FOUND = -1;\n\n    /**\n     * 返回开始位置，即起始字符位置（包含），未找到返回-1\n     *\n     * @param from 查找的开始位置（包含）\n     * @return 起始字符位置，未找到返回-1\n     */\n    int start(int from);\n\n    /**\n     * 返回结束位置，即最后一个字符后的位置（不包含）\n     *\n     * @param start 找到的起始位置\n     * @return 结束位置，未找到返回-1\n     */\n    int end(int start);\n\n    /**\n     * 复位查找器，用于重用对象\n     *\n     * @return this\n     */\n    default HuFinder reset() {\n        return this;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuHexUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\n class HuHexUtil {\n    public static final HuBase16Codec CODEC_LOWER = new HuBase16Codec(true);\n    public static final HuBase16Codec CODEC_UPPER = new HuBase16Codec(false);\n    public static void appendHex(StringBuilder builder, byte b, boolean toLowerCase) {\n        (toLowerCase ? CODEC_LOWER : CODEC_UPPER).appendHex(builder, b);\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuIoCopier.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nabstract class HuIoCopier<S, T> {\n\n    protected final int bufferSize;\n    protected final long count;\n\n    protected boolean flushEveryBuffer;\n\n    public HuIoCopier(int bufferSize, long count) {\n        this.bufferSize = bufferSize > 0 ? bufferSize : HuIoUtil.DEFAULT_BUFFER_SIZE;\n        this.count = count <= 0 ? Long.MAX_VALUE : count;\n    }\n\n    public abstract long copy(S source, T target);\n\n    protected int bufferSize(long count) {\n        return (int) Math.min(this.bufferSize, count);\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuIoRuntimeException.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.Serial;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuIoRuntimeException extends RuntimeException {\n    @Serial\n    private static final long serialVersionUID = 8247610319171014183L;\n\n    public HuIoRuntimeException(Throwable e) {\n        super(HuExceptionUtil.getMessage(e), e);\n    }\n\n    public HuIoRuntimeException(String message) {\n        super(message);\n    }\n\n    public HuIoRuntimeException(String messageTemplate, Object... params) {\n        super(HuStrUtil.format(messageTemplate, params));\n    }\n\n    public HuIoRuntimeException(String message, Throwable throwable) {\n        super(message, throwable);\n    }\n\n    public HuIoRuntimeException(Throwable throwable, String messageTemplate, Object... params) {\n        super(HuStrUtil.format(messageTemplate, params), throwable);\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuIoUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.*;\nimport java.nio.CharBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.Charset;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuIoUtil {\n\n    public static final int DEFAULT_BUFFER_SIZE = 2 << 12;\n\n    public static long copy(InputStream in, OutputStream out) throws HuIoRuntimeException {\n        return copy(in, out, DEFAULT_BUFFER_SIZE);\n    }\n\n    public static long copy(InputStream in, OutputStream out, int bufferSize) throws HuIoRuntimeException {\n        return new HuStreamCopier(bufferSize, -1).copy(in, out);\n    }\n\n    public static long copy(FileInputStream in, FileOutputStream out) throws HuIoRuntimeException {\n        HuAssert.notNull(in, \"FileInputStream is null!\");\n        HuAssert.notNull(out, \"FileOutputStream is null!\");\n\n        FileChannel inChannel = null;\n        FileChannel outChannel = null;\n        try {\n            inChannel = in.getChannel();\n            outChannel = out.getChannel();\n            return copy(inChannel, outChannel);\n        } finally {\n            close(outChannel);\n            close(inChannel);\n        }\n    }\n\n    public static FileInputStream toStream(File file) {\n        try {\n            return new FileInputStream(file);\n        } catch (FileNotFoundException e) {\n            throw new HuIoRuntimeException(e);\n        }\n    }\n\n    public static BufferedInputStream toBuffered(InputStream in) {\n        HuAssert.notNull(in, \"InputStream must be not null!\");\n        return (in instanceof BufferedInputStream) ? (BufferedInputStream) in : new BufferedInputStream(in);\n    }\n\n    public static void close(Closeable closeable) {\n        if (null != closeable) {\n            try {\n                closeable.close();\n            } catch (Exception e) {\n                // 静默关闭\n            }\n        }\n    }\n\n    public static long copy(FileChannel inChannel, FileChannel outChannel) throws HuIoRuntimeException {\n        HuAssert.notNull(inChannel, \"In channel is null!\");\n        HuAssert.notNull(outChannel, \"Out channel is null!\");\n\n        try {\n            return copySafely(inChannel, outChannel);\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        }\n    }\n\n    private static long copySafely(FileChannel inChannel, FileChannel outChannel) throws IOException {\n        final long totalBytes = inChannel.size();\n        for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失\n            final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数\n            pos += writeBytes;\n            remaining -= writeBytes;\n        }\n        return totalBytes;\n    }\n\n    public static BufferedReader getReader(InputStream in, Charset charset) {\n        if (null == in) {\n            return null;\n        }\n\n        InputStreamReader reader;\n        if (null == charset) {\n            reader = new InputStreamReader(in);\n        } else {\n            reader = new InputStreamReader(in, charset);\n        }\n\n        return new BufferedReader(reader);\n    }\n\n\n    public static String read(Reader reader) throws HuIoRuntimeException {\n        return read(reader, true);\n    }\n\n\n    public static String read(Reader reader, boolean isClose) throws HuIoRuntimeException {\n        final StringBuilder builder = new StringBuilder();\n        final CharBuffer buffer = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);\n        try {\n            while (-1 != reader.read(buffer)) {\n                builder.append(buffer.flip());\n            }\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        } finally {\n            if (isClose) {\n                HuIoUtil.close(reader);\n            }\n        }\n        return builder.toString();\n    }\n\n\n    public static byte[] readBytes(InputStream in) throws HuIoRuntimeException {\n        return readBytes(in, true);\n    }\n\n    public static byte[] readBytes(InputStream in, boolean isClose) throws HuIoRuntimeException {\n        if (in instanceof FileInputStream) {\n            // 文件流的长度是可预见的，此时直接读取效率更高\n            final byte[] result;\n            try {\n                final int available = in.available();\n                result = new byte[available];\n                final int readLength = in.read(result);\n                if (readLength != available) {\n                    throw new IOException(HuStrUtil.format(\"File length is [{}] but read [{}]!\", available, readLength));\n                }\n            } catch (IOException e) {\n                throw new HuIoRuntimeException(e);\n            } finally {\n                if (isClose) {\n                    close(in);\n                }\n            }\n            return result;\n        }\n\n        // 未知bytes总量的流\n        return read(in, isClose).toByteArray();\n    }\n\n    public static HuFastByteArrayOutputStream read(InputStream in, boolean isClose) throws HuIoRuntimeException {\n        final HuFastByteArrayOutputStream out;\n        if (in instanceof FileInputStream) {\n            // 文件流的长度是可预见的，此时直接读取效率更高\n            try {\n                out = new HuFastByteArrayOutputStream(in.available());\n            } catch (IOException e) {\n                throw new HuIoRuntimeException(e);\n            }\n        } else {\n            out = new HuFastByteArrayOutputStream();\n        }\n        try {\n            copy(in, out);\n        } finally {\n            if (isClose) {\n                close(in);\n            }\n        }\n        return out;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuNoResourceException.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.Serial;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuNoResourceException extends HuIoRuntimeException {\n    @Serial\n    private static final long serialVersionUID = -623254467603299129L;\n\n    public HuNoResourceException(String message) {\n        super(message);\n    }\n\n    public HuNoResourceException(String messageTemplate, Object... params) {\n        super(HuStrUtil.format(messageTemplate, params));\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuObjectUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuObjectUtil {\n\n    public static <T> T defaultIfNull(T source, Supplier<? extends T> defaultValueSupplier) {\n        if (isNull(source)) {\n            return defaultValueSupplier.get();\n        }\n        return source;\n    }\n\n    public static boolean isNull(Object obj) {\n        //noinspection ConstantConditions\n        return Objects.isNull(obj);\n    }\n\n    public static <T> T defaultIfNull(final T object, final T defaultValue) {\n        return isNull(object) ? defaultValue : object;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuPathUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.IOException;\nimport java.nio.file.*;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuPathUtil {\n\n    public static Path copyFile(Path src, Path target, CopyOption... options) throws HuIoRuntimeException {\n        HuAssert.notNull(src, \"Source File is null !\");\n        HuAssert.notNull(target, \"Destination File or directory is null !\");\n\n        final Path targetPath = isDirectory(target) ? target.resolve(src.getFileName()) : target;\n        // 创建级联父目录\n        mkParentDirs(targetPath);\n\n        try {\n            return Files.copy(src, targetPath, options);\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        }\n    }\n\n    public static Path copy(Path src, Path target, CopyOption... options) throws HuIoRuntimeException {\n        HuAssert.notNull(src, \"Src path must be not null !\");\n        HuAssert.notNull(target, \"Target path must be not null !\");\n\n        if (isDirectory(src)) {\n            return copyContent(src, target.resolve(src.getFileName()), options);\n        }\n        return copyFile(src, target, options);\n    }\n\n    public static Path copyContent(Path src, Path target, CopyOption... options) throws HuIoRuntimeException {\n        HuAssert.notNull(src, \"Src path must be not null !\");\n        HuAssert.notNull(target, \"Target path must be not null !\");\n\n        try {\n            Files.walkFileTree(src, new HuCopyVisitor(src, target, options));\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        }\n        return target;\n    }\n\n    public static boolean isDirectory(Path path) {\n        return isDirectory(path, false);\n    }\n\n    public static boolean isDirectory(Path path, boolean isFollowLinks) {\n        if (null == path) {\n            return false;\n        }\n\n        final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};\n        return Files.isDirectory(path, options);\n    }\n\n    public static boolean equals(Path file1, Path file2) throws HuIoRuntimeException {\n        try {\n            return Files.isSameFile(file1, file2);\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        }\n    }\n\n\n    public static boolean exists(Path path, boolean isFollowLinks) {\n        final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};\n        return Files.exists(path, options);\n    }\n\n\n    public static Path mkdir(Path dir) {\n        if (null != dir && !exists(dir, false)) {\n            try {\n                Files.createDirectories(dir);\n            } catch (IOException e) {\n                throw new HuIoRuntimeException(e);\n            }\n        }\n        return dir;\n    }\n\n    public static Path mkParentDirs(Path path) {\n        return mkdir(path.getParent());\n    }\n\n    public static String getName(Path path) {\n        if (null == path) {\n            return null;\n        }\n        return path.getFileName().toString();\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuPercentCodec.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.util.BitSet;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuPercentCodec implements Serializable {\n    @Serial\n    private static final long serialVersionUID = 1L;\n    private final BitSet safeCharacters;\n\n    private boolean encodeSpaceAsPlus = false;\n\n    public static HuPercentCodec of(HuPercentCodec codec) {\n        return new HuPercentCodec((BitSet) codec.safeCharacters.clone());\n    }\n\n    public static HuPercentCodec of(CharSequence chars) {\n        HuAssert.notNull(chars, \"chars must not be null\");\n        final HuPercentCodec codec = new HuPercentCodec();\n        final int length = chars.length();\n        for (int i = 0; i < length; i++) {\n            codec.addSafe(chars.charAt(i));\n        }\n        return codec;\n    }\n\n    public HuPercentCodec() {\n        this(new BitSet(256));\n    }\n\n    public HuPercentCodec(BitSet safeCharacters) {\n        this.safeCharacters = safeCharacters;\n    }\n\n    public HuPercentCodec addSafe(char c) {\n        safeCharacters.set(c);\n        return this;\n    }\n\n\n    public HuPercentCodec or(HuPercentCodec codec) {\n        this.safeCharacters.or(codec.safeCharacters);\n        return this;\n    }\n\n    public HuPercentCodec orNew(HuPercentCodec codec) {\n        return of(this).or(codec);\n    }\n\n\n    public String encode(CharSequence path, Charset charset, char... customSafeChar) {\n        if (null == charset || HuStrUtil.isEmpty(path)) {\n            return HuStrUtil.str(path);\n        }\n\n        final StringBuilder rewrittenPath = new StringBuilder(path.length());\n        final ByteArrayOutputStream buf = new ByteArrayOutputStream();\n        final OutputStreamWriter writer = new OutputStreamWriter(buf, charset);\n\n        char c;\n        for (int i = 0; i < path.length(); i++) {\n            c = path.charAt(i);\n            if (safeCharacters.get(c) || HuArrayUtil.contains(customSafeChar, c)) {\n                rewrittenPath.append(c);\n            } else if (encodeSpaceAsPlus && c == HuCharUtil.SPACE) {\n                // 对于空格单独处理\n                rewrittenPath.append('+');\n            } else {\n                // convert to external encoding before hex conversion\n                try {\n                    writer.write(c);\n                    writer.flush();\n                } catch (IOException e) {\n                    buf.reset();\n                    continue;\n                }\n\n                // 兼容双字节的Unicode符处理（如部分emoji）\n                byte[] ba = buf.toByteArray();\n                for (byte toEncode : ba) {\n                    // Converting each byte in the buffer\n                    rewrittenPath.append('%');\n                    HuHexUtil.appendHex(rewrittenPath, toEncode, false);\n                }\n                buf.reset();\n            }\n        }\n        return rewrittenPath.toString();\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuResource.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.nio.charset.Charset;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\ninterface HuResource {\n\n    /**\n     * 获取资源名，例如文件资源的资源名为文件名\n     *\n     * @return 资源名\n     */\n    String getName();\n\n    /**\n     * 获得解析后的{@link URL}，无对应URL的返回{@code null}\n     *\n     * @return 解析后的{@link URL}\n     */\n    URL getUrl();\n\n    /**\n     * 获得 {@link InputStream}\n     *\n     * @return {@link InputStream}\n     */\n    InputStream getStream();\n\n    /**\n     * 获得Reader\n     *\n     * @param charset 编码\n     * @return {@link BufferedReader}\n     */\n    default BufferedReader getReader(Charset charset) {\n        return HuIoUtil.getReader(getStream(), charset);\n    }\n\n    /**\n     * 读取资源内容，读取完毕后会关闭流<br>\n     * 关闭流并不影响下一次读取\n     *\n     * @param charset 编码\n     * @return 读取资源内容\n     * @throws HuIoRuntimeException 包装{@link IOException}\n     */\n    default String readStr(Charset charset) throws HuIoRuntimeException {\n        return HuIoUtil.read(getReader(charset));\n    }\n\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuResourceUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.net.URL;\nimport java.nio.charset.Charset;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuResourceUtil {\n\n    public static String readStr(String resource, Charset charset) {\n        return getResourceObj(resource).readStr(charset);\n    }\n\n    public static HuResource getResourceObj(String path) {\n        if (HuStrUtil.isNotBlank(path)) {\n            if (path.startsWith(HuUrlUtil.FILE_URL_PREFIX) || HuFileUtil.isAbsolutePath(path)) {\n                return new HuFileResource(path);\n            }\n        }\n\n        return new HuClassPathResource(path);\n    }\n\n    public static URL getResource(String resource) throws HuIoRuntimeException {\n        return getResource(resource, null);\n    }\n\n    public static URL getResource(String resource, Class<?> baseClass) {\n        resource = HuStrUtil.nullToEmpty(resource);\n        return (null != baseClass) ? baseClass.getResource(resource) : HuClassLoaderUtil.getClassLoader().getResource(resource);\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuRfc3986.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuRfc3986 {\n\n    public static final HuPercentCodec PATH = HuPercentCodec\n            .of(unreservedChars())\n            .orNew(HuPercentCodec.of(\"!$&'()*+,;=\"))\n            .or(HuPercentCodec.of(\":@\"))\n            .orNew(HuPercentCodec.of(\"/\"));\n\n    private static StringBuilder unreservedChars() {\n        StringBuilder sb = new StringBuilder();\n\n        // ALPHA\n        for (char c = 'A'; c <= 'Z'; c++) {\n            sb.append(c);\n        }\n        for (char c = 'a'; c <= 'z'; c++) {\n            sb.append(c);\n        }\n\n        // DIGIT\n        for (char c = '0'; c <= '9'; c++) {\n            sb.append(c);\n        }\n\n        // \"-\" / \".\" / \"_\" / \"~\"\n        sb.append(\"_.-~\");\n\n        return sb;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuSplitIter.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Function;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuSplitIter extends HuComputeIter<String> implements Serializable {\n    private final String text;\n    private final HuTextFinder finder;\n    private final int limit;\n    private final boolean ignoreEmpty;\n    private int offset;\n    private int count;\n\n    public HuSplitIter(CharSequence text, HuTextFinder separatorFinder, int limit, boolean ignoreEmpty) {\n        HuAssert.notNull(text, \"Text must be not null!\");\n        this.text = text.toString();\n        this.finder = separatorFinder.setText(text);\n        this.limit = limit > 0 ? limit : Integer.MAX_VALUE;\n        this.ignoreEmpty = ignoreEmpty;\n    }\n\n    @Override\n    protected String computeNext() {\n        // 达到数量上限或末尾，结束\n        if (count >= limit || offset > text.length()) {\n            return null;\n        }\n\n        // 达到数量上限\n        if (count == (limit - 1)) {\n            // 当到达限制次数时，最后一个元素为剩余部分\n            if (ignoreEmpty && offset == text.length()) {\n                // 最后一个是空串\n                return null;\n            }\n\n            // 结尾整个作为一个元素\n            count++;\n            return text.substring(offset);\n        }\n\n        final int start = finder.start(offset);\n        // 无分隔符，结束\n        if (start < 0) {\n            // 如果不再有分隔符，但是遗留了字符，则单独作为一个段\n            if (offset <= text.length()) {\n                final String result = text.substring(offset);\n                if (!ignoreEmpty || !result.isEmpty()) {\n                    // 返回非空串\n                    offset = Integer.MAX_VALUE;\n                    return result;\n                }\n            }\n            return null;\n        }\n\n        // 找到新的分隔符位置\n        final String result = text.substring(offset, start);\n        offset = finder.end(start);\n\n        if (ignoreEmpty && result.isEmpty()) {\n            // 发现空串且需要忽略时，跳过之\n            return computeNext();\n        }\n\n        count++;\n        return result;\n    }\n\n    /**\n     * 重置\n     */\n    public void reset() {\n        this.finder.reset();\n        this.offset = 0;\n        this.count = 0;\n    }\n\n    public List<String> toList(boolean trim) {\n        return toList((str) -> trim ? HuStrUtil.trim(str) : str);\n    }\n\n    public <T> List<T> toList(Function<String, T> mapping) {\n        final List<T> result = new ArrayList<>();\n        while (this.hasNext()) {\n            final T apply = mapping.apply(this.next());\n            if (ignoreEmpty && HuStrUtil.isEmptyIfStr(apply)) {\n                // 对于mapping之后依旧是String的情况，ignoreEmpty依旧有效\n                continue;\n            }\n            result.add(apply);\n        }\n        if (result.isEmpty()) {\n            return new ArrayList<>(0);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuStrFinder.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuStrFinder extends HuTextFinder {\n\n    private final CharSequence strToFind;\n    private final boolean caseInsensitive;\n\n    public HuStrFinder(CharSequence strToFind, boolean caseInsensitive) {\n        HuAssert.notEmpty(strToFind);\n        this.strToFind = strToFind;\n        this.caseInsensitive = caseInsensitive;\n    }\n\n    @Override\n    public int start(int from) {\n        HuAssert.notNull(this.text, \"Text to find must be not null!\");\n        final int subLen = strToFind.length();\n\n        if (from < 0) {\n            from = 0;\n        }\n        int endLimit = getValidEndIndex();\n        if (negative) {\n            for (int i = from; i > endLimit; i--) {\n                if (HuStrUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) {\n                    return i;\n                }\n            }\n        } else {\n            endLimit = endLimit - subLen + 1;\n            for (int i = from; i < endLimit; i++) {\n                if (HuStrUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) {\n                    return i;\n                }\n            }\n        }\n\n        return INDEX_NOT_FOUND;\n    }\n\n    @Override\n    public int end(int start) {\n        if (start < 0) {\n            return -1;\n        }\n        return start + strToFind.length();\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuStrFormatter.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.util.Map;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuStrFormatter {\n    public static String format(String strPattern, Object... argArray) {\n        return formatWith(strPattern, HuStrUtil.EMPTY_JSON, argArray);\n    }\n\n    public static String formatWith(String strPattern, String placeHolder, Object... argArray) {\n        if (HuStrUtil.isBlank(strPattern) || HuStrUtil.isBlank(placeHolder) || HuArrayUtil.isEmpty(argArray)) {\n            return strPattern;\n        }\n        final int strPatternLength = strPattern.length();\n        final int placeHolderLength = placeHolder.length();\n\n        // 初始化定义好的长度以获得更好的性能\n        final StringBuilder sbuf = new StringBuilder(strPatternLength + 50);\n\n        int handledPosition = 0;\n        // 记录已经处理到的位置\n        int delimIndex;\n        // 占位符所在位置\n        for (int argIndex = 0; argIndex < argArray.length; argIndex++) {\n            delimIndex = strPattern.indexOf(placeHolder, handledPosition);\n            if (delimIndex == -1) {\n                // 剩余部分无占位符\n                if (handledPosition == 0) {\n                    // 不带占位符的模板直接返回\n                    return strPattern;\n                }\n                // 字符串模板剩余部分不再包含占位符，加入剩余部分后返回结果\n                sbuf.append(strPattern, handledPosition, strPatternLength);\n                return sbuf.toString();\n            }\n\n            // 转义符\n            if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == HuStrUtil.C_BACKSLASH) {\n                if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == HuStrUtil.C_BACKSLASH) {\n                    // 转义符之前还有一个转义符，占位符依旧有效\n                    sbuf.append(strPattern, handledPosition, delimIndex - 1);\n                    sbuf.append(HuStrUtil.utf8Str(argArray[argIndex]));\n                    handledPosition = delimIndex + placeHolderLength;\n                } else {\n                    // 占位符被转义\n                    argIndex--;\n                    sbuf.append(strPattern, handledPosition, delimIndex - 1);\n                    sbuf.append(placeHolder.charAt(0));\n                    handledPosition = delimIndex + 1;\n                }\n            } else {// 正常占位符\n                sbuf.append(strPattern, handledPosition, delimIndex);\n                sbuf.append(HuStrUtil.utf8Str(argArray[argIndex]));\n                handledPosition = delimIndex + placeHolderLength;\n            }\n        }\n\n        // 加入最后一个占位符后所有的字符\n        sbuf.append(strPattern, handledPosition, strPatternLength);\n\n        return sbuf.toString();\n    }\n\n    public static String format(CharSequence template, Map<?, ?> map, boolean ignoreNull) {\n        if (null == template) {\n            return null;\n        }\n        if (null == map || map.isEmpty()) {\n            return template.toString();\n        }\n\n        String template2 = template.toString();\n        String value;\n        for (Map.Entry<?, ?> entry : map.entrySet()) {\n            value = HuStrUtil.utf8Str(entry.getValue());\n            if (null == value && ignoreNull) {\n                continue;\n            }\n            template2 = HuStrUtil.replace(template2, \"{\" + entry.getKey() + \"}\", value);\n        }\n        return template2;\n    }\n}\n\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuStrUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Predicate;\nimport java.util.function.UnaryOperator;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuStrUtil {\n\n    public static final char C_SLASH = HuCharUtil.SLASH;\n\n\n    public static final char C_BACKSLASH = HuCharUtil.BACKSLASH;\n\n    public static final String DOT = \".\";\n\n\n    public static final String DOUBLE_DOT = \"..\";\n\n\n    public static final String SLASH = \"/\";\n\n\n    public static final String COLON = \":\";\n\n\n    public static final String EMPTY_JSON = \"{}\";\n\n    public static final String EMPTY = \"\";\n\n    public static final int INDEX_NOT_FOUND = -1;\n    public static final String NULL = \"null\";\n\n    public static String utf8Str(Object obj) {\n        return str(obj, HuCharsetUtil.CHARSET_UTF_8);\n    }\n\n    public static String str(Object obj, Charset charset) {\n        if (null == obj) {\n            return null;\n        }\n\n        if (obj instanceof String value) {\n            return value;\n        } else if (obj instanceof byte[] value) {\n            return str(value, charset);\n        } else if (obj instanceof Byte[] value) {\n            return str(value, charset);\n        } else if (obj instanceof ByteBuffer value) {\n            return str(value, charset);\n        } else if (HuArrayUtil.isArray(obj)) {\n            return HuArrayUtil.toString(obj);\n        }\n\n        return obj.toString();\n    }\n\n    public static String str(Byte[] data, Charset charset) {\n        if (data == null) {\n            return null;\n        }\n\n        byte[] bytes = new byte[data.length];\n        Byte dataByte;\n        for (int i = 0; i < data.length; i++) {\n            dataByte = data[i];\n            bytes[i] = (null == dataByte) ? -1 : dataByte;\n        }\n\n        return str(bytes, charset);\n    }\n\n    public static String str(byte[] data, Charset charset) {\n        if (data == null) {\n            return null;\n        }\n\n        if (null == charset) {\n            return new String(data);\n        }\n        return new String(data, charset);\n    }\n\n    public static String str(ByteBuffer data, Charset charset) {\n        if (null == charset) {\n            charset = Charset.defaultCharset();\n        }\n        return charset.decode(data).toString();\n    }\n\n    public static boolean isBlank(CharSequence str) {\n        final int length;\n        if ((str == null) || ((length = str.length()) == 0)) {\n            return true;\n        }\n\n        for (int i = 0; i < length; i++) {\n            // 只要有一个非空字符即为非空字符串\n            if (!HuCharUtil.isBlankChar(str.charAt(i))) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    public static boolean isNotBlank(CharSequence str) {\n        return !isBlank(str);\n    }\n\n    public static String format(CharSequence template, Object... params) {\n        if (null == template) {\n            return NULL;\n        }\n        if (HuArrayUtil.isEmpty(params) || isBlank(template)) {\n            return template.toString();\n        }\n        return HuStrFormatter.format(template.toString(), params);\n    }\n\n    public static String format(CharSequence template, Map<?, ?> map) {\n        return HuStrFormatter.format(template, map, true);\n    }\n\n    public static String cleanBlank(CharSequence str) {\n        return filter(str, c -> !HuCharUtil.isBlankChar(c));\n    }\n\n    public static String filter(CharSequence str, final HuFilter<Character> filter) {\n        if (str == null || filter == null) {\n            return str(str);\n        }\n\n        int len = str.length();\n        final StringBuilder sb = new StringBuilder(len);\n        char c;\n        for (int i = 0; i < len; i++) {\n            c = str.charAt(i);\n            if (filter.accept(c)) {\n                sb.append(c);\n            }\n        }\n        return sb.toString();\n    }\n\n    public static String replace(CharSequence str, CharSequence searchStr, CharSequence replacement) {\n        return replace(str, 0, searchStr, replacement, false);\n    }\n\n    public static boolean contains(CharSequence str, CharSequence searchStr) {\n        if (null == str || null == searchStr) {\n            return false;\n        }\n        return str.toString().contains(searchStr);\n    }\n\n    public static String replace(CharSequence str, int fromIndex, CharSequence searchStr, CharSequence replacement, boolean ignoreCase) {\n        if (isEmpty(str) || isEmpty(searchStr)) {\n            return str(str);\n        }\n        if (null == replacement) {\n            replacement = EMPTY;\n        }\n\n        final int strLength = str.length();\n        final int searchStrLength = searchStr.length();\n        if (strLength < searchStrLength) {\n            // issue#I4M16G@Gitee\n            return str(str);\n        }\n\n        if (fromIndex > strLength) {\n            return str(str);\n        } else if (fromIndex < 0) {\n            fromIndex = 0;\n        }\n\n        final StringBuilder result = new StringBuilder(strLength - searchStrLength + replacement.length());\n        if (0 != fromIndex) {\n            result.append(str.subSequence(0, fromIndex));\n        }\n\n        int preIndex = fromIndex;\n        int index;\n        while ((index = indexOf(str, searchStr, preIndex, ignoreCase)) > -1) {\n            result.append(str.subSequence(preIndex, index));\n            result.append(replacement);\n            preIndex = index + searchStrLength;\n        }\n\n        if (preIndex < strLength) {\n            // 结尾部分\n            result.append(str.subSequence(preIndex, strLength));\n        }\n        return result.toString();\n    }\n\n    public static int indexOf(CharSequence text, CharSequence searchStr, int from, boolean ignoreCase) {\n        if (isEmpty(text) || isEmpty(searchStr)) {\n            if (HuStrUtil.equals(text, searchStr)) {\n                return 0;\n            } else {\n                return INDEX_NOT_FOUND;\n            }\n        }\n        return new HuStrFinder(searchStr, ignoreCase).setText(text).start(from);\n    }\n\n    public static boolean equals(CharSequence str1, CharSequence str2) {\n        return equals(str1, str2, false);\n    }\n\n    public static List<String> split(CharSequence text, char separator) {\n\n        if (null == text) {\n            return new ArrayList<>(0);\n        }\n\n        final HuSplitIter splitIter = new HuSplitIter(text, new HuCharFinder(separator), 0, false);\n\n        UnaryOperator<String> mapping = str -> str;\n        return splitIter.toList(mapping);\n    }\n\n    public static String nullToEmpty(CharSequence str) {\n        return nullToDefault(str, EMPTY);\n    }\n\n    public static String trim(CharSequence str) {\n        return (null == str) ? null : trim(str, 0);\n    }\n\n\n    public static boolean isEmptyIfStr(Object obj) {\n        if (null == obj) {\n            return true;\n        } else if (obj instanceof CharSequence cs) {\n            return 0 == cs.length();\n        }\n\n        return false;\n    }\n\n\n    public static String removePrefix(CharSequence str, CharSequence prefix) {\n        if (isEmpty(str) || isEmpty(prefix)) {\n            return str(str);\n        }\n\n        final String str2 = str.toString();\n        if (str2.startsWith(prefix.toString())) {\n            return subSuf(str2, prefix.length());// 截取后半段\n        }\n        return str2;\n    }\n\n\n    public static String nullToDefault(CharSequence str, String defaultStr) {\n        return (str == null) ? defaultStr : str.toString();\n    }\n\n    public static boolean isEmpty(CharSequence str) {\n        return str == null || str.length() == 0;\n    }\n\n    public static String trimStart(CharSequence str) {\n        return trim(str, -1);\n    }\n\n    public static String trim(CharSequence str, int mode) {\n        return trim(str, mode, HuCharUtil::isBlankChar);\n    }\n\n    public static String trim(CharSequence str, int mode, Predicate<Character> predicate) {\n        String result;\n        if (str == null) {\n            result = null;\n        } else {\n            int length = str.length();\n            int start = 0;\n            int end = length;// 扫描字符串头部\n            if (mode <= 0) {\n                while ((start < end) && (predicate.test(str.charAt(start)))) {\n                    start++;\n                }\n            }// 扫描字符串尾部\n            if (mode >= 0) {\n                while ((start < end) && (predicate.test(str.charAt(end - 1)))) {\n                    end--;\n                }\n            }\n            if ((start > 0) || (end < length)) {\n                result = str.toString().substring(start, end);\n            } else {\n                result = str.toString();\n            }\n        }\n\n        return result;\n    }\n\n    public static String removePrefixIgnoreCase(CharSequence str, CharSequence prefix) {\n        if (isEmpty(str) || isEmpty(prefix)) {\n            return str(str);\n        }\n\n        final String str2 = str.toString();\n        if (startWithIgnoreCase(str, prefix)) {\n            return subSuf(str2, prefix.length());// 截取后半段\n        }\n        return str2;\n    }\n\n    public static String str(CharSequence cs) {\n        return null == cs ? null : cs.toString();\n    }\n\n    public static boolean startWithIgnoreCase(CharSequence str, CharSequence prefix) {\n        return startWith(str, prefix, true);\n    }\n\n    public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase) {\n        return startWith(str, prefix, ignoreCase, false);\n    }\n\n    public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase, boolean ignoreEquals) {\n        if (null == str || null == prefix) {\n            if (ignoreEquals) {\n                return false;\n            }\n            return null == str && null == prefix;\n        }\n\n        boolean isStartWith = str.toString()\n                .regionMatches(ignoreCase, 0, prefix.toString(), 0, prefix.length());\n\n        if (isStartWith) {\n            return (!ignoreEquals) || (!equals(str, prefix, ignoreCase));\n        }\n        return false;\n    }\n\n\n    public static boolean startWith(CharSequence str, char c) {\n        if (isEmpty(str)) {\n            return false;\n        }\n        return c == str.charAt(0);\n    }\n\n    public static String subSuf(CharSequence string, int fromIndex) {\n        if (isEmpty(string)) {\n            return null;\n        }\n        return sub(string, fromIndex, string.length());\n    }\n\n    public static String sub(CharSequence str, int fromIndexInclude, int toIndexExclude) {\n        if (isEmpty(str)) {\n            return str(str);\n        }\n        int len = str.length();\n\n        if (fromIndexInclude < 0) {\n            fromIndexInclude = len + fromIndexInclude;\n            if (fromIndexInclude < 0) {\n                fromIndexInclude = 0;\n            }\n        } else if (fromIndexInclude > len) {\n            fromIndexInclude = len;\n        }\n\n        if (toIndexExclude < 0) {\n            toIndexExclude = len + toIndexExclude;\n            if (toIndexExclude < 0) {\n                toIndexExclude = len;\n            }\n        } else if (toIndexExclude > len) {\n            toIndexExclude = len;\n        }\n\n        if (toIndexExclude < fromIndexInclude) {\n            int tmp = fromIndexInclude;\n            fromIndexInclude = toIndexExclude;\n            toIndexExclude = tmp;\n        }\n\n        if (fromIndexInclude == toIndexExclude) {\n            return EMPTY;\n        }\n\n        return str.toString().substring(fromIndexInclude, toIndexExclude);\n    }\n\n    public static boolean equals(CharSequence str1, CharSequence str2, boolean ignoreCase) {\n        if (null == str1) {\n            // 只有两个都为null才判断相等\n            return str2 == null;\n        }\n        if (null == str2) {\n            // 字符串2空，字符串1非空，直接false\n            return false;\n        }\n\n        if (ignoreCase) {\n            return str1.toString().equalsIgnoreCase(str2.toString());\n        } else {\n            return str1.toString().contentEquals(str2);\n        }\n    }\n\n    public static boolean isSubEquals(CharSequence str1, int start1, CharSequence str2, int start2, int length, boolean ignoreCase) {\n        if (null == str1 || null == str2) {\n            return false;\n        }\n\n        return str1.toString().regionMatches(ignoreCase, start1, str2.toString(), start2, length);\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuStreamCopier.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuStreamCopier extends HuIoCopier<InputStream, OutputStream> {\n\n\n    public HuStreamCopier(int bufferSize, long count) {\n        super(bufferSize, count);\n    }\n\n    @Override\n    public long copy(InputStream source, OutputStream target) {\n        HuAssert.notNull(source, \"InputStream is null !\");\n        HuAssert.notNull(target, \"OutputStream is null !\");\n\n\n        final long size;\n        try {\n            size = doCopy(source, target, new byte[bufferSize(this.count)]);\n            target.flush();\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        }\n\n\n        return size;\n    }\n\n    private long doCopy(InputStream source, OutputStream target, byte[] buffer) throws IOException {\n        long numToRead = this.count > 0 ? this.count : Long.MAX_VALUE;\n        long total = 0;\n\n        int read;\n        while (numToRead > 0) {\n            read = source.read(buffer, 0, bufferSize(numToRead));\n            if (read < 0) {\n                // 提前读取到末尾\n                break;\n            }\n            target.write(buffer, 0, read);\n            if (flushEveryBuffer) {\n                target.flush();\n            }\n\n            numToRead -= read;\n            total += read;\n\n        }\n\n        return total;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuTextFinder.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.Serializable;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nabstract class HuTextFinder implements HuFinder, Serializable {\n\n    protected CharSequence text;\n    protected int endIndex = -1;\n    protected boolean negative;\n\n    public HuTextFinder setText(CharSequence text) {\n        this.text = HuAssert.notNull(text, \"Text must be not null!\");\n        return this;\n    }\n\n\n    protected int getValidEndIndex() {\n        if (negative && -1 == endIndex) {\n            // 反向查找模式下，-1表示0前面的位置，即字符串反向末尾的位置\n            return -1;\n        }\n        final int limit;\n        if (endIndex < 0) {\n            limit = endIndex + text.length() + 1;\n        } else {\n            limit = Math.min(endIndex, text.length());\n        }\n        return limit;\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuUrlResource.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.io.Serializable;\nimport java.net.URL;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuUrlResource implements HuResource, Serializable {\n\n    protected URL url;\n    protected String name;\n\n    public HuUrlResource(URL url) {\n        this(url, null);\n    }\n\n    public HuUrlResource(URL url, String name) {\n        this.url = url;\n\n        this.name = HuObjectUtil.defaultIfNull(name, () -> (null != url ? HuFileUtil.getName(url.getPath()) : null));\n    }\n\n    @Override\n    public String getName() {\n        return this.name;\n    }\n\n    @Override\n    public URL getUrl() {\n        return this.url;\n    }\n\n    @Override\n    public InputStream getStream() throws HuNoResourceException {\n        if (null == this.url) {\n            throw new HuNoResourceException(\"Resource URL is null!\");\n        }\n        return HuUrlUtil.getStream(url);\n    }\n\n\n    /**\n     * 获得File\n     *\n     * @return {@link File}\n     */\n    public File getFile() {\n        return HuFileUtil.file(this.url);\n    }\n\n    /**\n     * 返回路径\n     *\n     * @return 返回URL路径\n     */\n    @Override\n    public String toString() {\n        return (null == this.url) ? \"null\" : this.url.toString();\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuUrlUtil.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.Charset;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuUrlUtil {\n\n    public static final String CLASSPATH_URL_PREFIX = \"classpath:\";\n    public static final String FILE_URL_PREFIX = \"file:\";\n\n    public static URL url(URI uri) throws HuUtilException {\n        if (null == uri) {\n            return null;\n        }\n        try {\n            return uri.toURL();\n        } catch (MalformedURLException e) {\n            throw new HuUtilException(e);\n        }\n    }\n\n    public static InputStream getStream(URL url) {\n        HuAssert.notNull(url, \"URL must be not null\");\n        try {\n            return url.openStream();\n        } catch (IOException e) {\n            throw new HuIoRuntimeException(e);\n        }\n    }\n\n    public static URL getURL(File file) {\n        HuAssert.notNull(file, \"File is null !\");\n        try {\n            return file.toURI().toURL();\n        } catch (MalformedURLException e) {\n            throw new HuUtilException(e, \"Error occured when get URL!\");\n        }\n    }\n\n    public static String getDecodedPath(URL url) {\n        if (null == url) {\n            return null;\n        }\n\n        String path = null;\n        try {\n            // URL对象的getPath方法对于包含中文或空格的问题\n            path = toURI(url).getPath();\n        } catch (HuUtilException e) {\n            // ignore\n        }\n        return (null != path) ? path : url.getPath();\n    }\n\n    public static URI toURI(URL url) throws HuUtilException {\n        return toURI(url, false);\n    }\n\n    public static URI toURI(URL url, boolean isEncode) throws HuUtilException {\n        if (null == url) {\n            return null;\n        }\n\n        return toURI(url.toString(), isEncode);\n    }\n\n    public static URI toURI(String location, boolean isEncode) throws HuUtilException {\n        if (isEncode) {\n            location = encode(location);\n        }\n        try {\n            return new URI(HuStrUtil.trim(location));\n        } catch (URISyntaxException e) {\n            throw new HuUtilException(e);\n        }\n    }\n\n    public static String encode(String url) throws HuUtilException {\n        return encode(url, HuCharsetUtil.CHARSET_UTF_8);\n    }\n\n    public static String encode(String url, Charset charset) {\n        return HuRfc3986.PATH.encode(url, charset);\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/HuUtilException.java",
    "content": "package com.iohao.game.common.kit.adapter;\n\n\nimport java.io.Serial;\n\n/**\n * @author 渔民小镇\n * @date 2023-01-19\n */\nclass HuUtilException extends RuntimeException {\n    @Serial\n    private static final long serialVersionUID = 8247610319171014183L;\n\n    public HuUtilException(Throwable e) {\n        super(HuExceptionUtil.getMessage(e), e);\n    }\n\n    public HuUtilException(String messageTemplate, Object... params) {\n        super(HuStrUtil.format(messageTemplate, params));\n    }\n\n    public HuUtilException(Throwable throwable, String messageTemplate, Object... params) {\n        super(HuStrUtil.format(messageTemplate, params), throwable);\n    }\n}\n"
  },
  {
    "path": "widget/other-tool/src/main/java/com/iohao/game/common/kit/adapter/package-info.java",
    "content": "/*\n * ioGame\n * Copyright (C) 2021 - present  渔民小镇 （262610965@qq.com、luoyizhu@gmail.com） . All Rights Reserved.\n * # iohao.com . 渔民小镇\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/**\n * 工具相关 - 临时适配的工具\n *\n * @author 渔民小镇\n * @date 2024-08-22\n */\npackage com.iohao.game.common.kit.adapter;"
  },
  {
    "path": "widget/other-tool/src/main/java/module-info.java.txt",
    "content": "open module com.iohao.game.common.kit.other.tool {\n    requires transitive lombok;\n\n    exports com.iohao.game.common.kit.hutool;\n}"
  }
]