Repository: iohao/iogame
Branch: main
Commit: 917a7964ccf3
Files: 906
Total size: 2.4 MB
Directory structure:
gitextract_1je6oco0/
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── ask-question.md
│ ├── bug-report.md
│ ├── empty-issues.md
│ └── enhance-task.md
├── .gitignore
├── BACKERS.md
├── LICENSE
├── README.md
├── README_CN.md
├── changeLog_ioGame.md
├── common/
│ ├── README.md
│ ├── common-core/
│ │ ├── README.md
│ │ ├── pom.xml
│ │ └── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── iohao/
│ │ │ │ └── game/
│ │ │ │ └── action/
│ │ │ │ └── skeleton/
│ │ │ │ ├── IoGameVersion.java
│ │ │ │ ├── annotation/
│ │ │ │ │ ├── ActionController.java
│ │ │ │ │ ├── ActionMethod.java
│ │ │ │ │ ├── DocActionSend.java
│ │ │ │ │ ├── DocActionSends.java
│ │ │ │ │ ├── ValidatedGroup.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── core/
│ │ │ │ │ ├── ActionCommand.java
│ │ │ │ │ ├── ActionCommandDocParser.java
│ │ │ │ │ ├── ActionCommandFlowExecute.java
│ │ │ │ │ ├── ActionCommandHandler.java
│ │ │ │ │ ├── ActionCommandParser.java
│ │ │ │ │ ├── ActionCommandRegion.java
│ │ │ │ │ ├── ActionCommandRegionGlobalCheckKit.java
│ │ │ │ │ ├── ActionCommandRegions.java
│ │ │ │ │ ├── ActionFactoryBean.java
│ │ │ │ │ ├── ActionParserListenerAbout.java
│ │ │ │ │ ├── ActionSend.java
│ │ │ │ │ ├── BarMessageKit.java
│ │ │ │ │ ├── BarSkeleton.java
│ │ │ │ │ ├── BarSkeletonBuilder.java
│ │ │ │ │ ├── BarSkeletonBuilderParamConfig.java
│ │ │ │ │ ├── BarSkeletonSetting.java
│ │ │ │ │ ├── CmdInfo.java
│ │ │ │ │ ├── CmdInfoFlyweightFactory.java
│ │ │ │ │ ├── CmdKit.java
│ │ │ │ │ ├── DataCodecKit.java
│ │ │ │ │ ├── DefaultActionCommandFlowExecute.java
│ │ │ │ │ ├── DefaultActionFactoryBean.java
│ │ │ │ │ ├── DependencyInjectionPart.java
│ │ │ │ │ ├── DevConfig.java
│ │ │ │ │ ├── Handler.java
│ │ │ │ │ ├── InOutManager.java
│ │ │ │ │ ├── InOutManagerAbout.java
│ │ │ │ │ ├── IoGameCommonCoreConfig.java
│ │ │ │ │ ├── IoGameGlobalSetting.java
│ │ │ │ │ ├── PrintActionKit.java
│ │ │ │ │ ├── SkeletonAttr.java
│ │ │ │ │ ├── ValidatorKit.java
│ │ │ │ │ ├── action/
│ │ │ │ │ │ └── parser/
│ │ │ │ │ │ ├── ActionParserContext.java
│ │ │ │ │ │ ├── ActionParserListener.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── codec/
│ │ │ │ │ │ ├── DataCodec.java
│ │ │ │ │ │ ├── DataSelfEncode.java
│ │ │ │ │ │ ├── JsonDataCodec.java
│ │ │ │ │ │ ├── ProtoDataCodec.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── commumication/
│ │ │ │ │ │ ├── BroadcastContext.java
│ │ │ │ │ │ ├── BroadcastOrderContext.java
│ │ │ │ │ │ ├── BrokerClientContext.java
│ │ │ │ │ │ ├── ChannelContext.java
│ │ │ │ │ │ ├── CommunicationAggregationContext.java
│ │ │ │ │ │ ├── InvokeExternalModuleContext.java
│ │ │ │ │ │ ├── InvokeModuleContext.java
│ │ │ │ │ │ ├── ProcessorContext.java
│ │ │ │ │ │ ├── SimpleServer.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── doc/
│ │ │ │ │ │ ├── ActionCommandDoc.java
│ │ │ │ │ │ ├── ActionCommandDocKit.java
│ │ │ │ │ │ ├── ActionDoc.java
│ │ │ │ │ │ ├── ActionDocs.java
│ │ │ │ │ │ ├── ActionDocument.java
│ │ │ │ │ │ ├── ActionMemberCmdDocument.java
│ │ │ │ │ │ ├── ActionMethodDocument.java
│ │ │ │ │ │ ├── ActionSendDoc.java
│ │ │ │ │ │ ├── ActionSendDocs.java
│ │ │ │ │ │ ├── ActionSendDocsRegion.java
│ │ │ │ │ │ ├── BarSkeletonDoc.java
│ │ │ │ │ │ ├── BroadcastDoc.java
│ │ │ │ │ │ ├── BroadcastDocBuilder.java
│ │ │ │ │ │ ├── BroadcastDocument.java
│ │ │ │ │ │ ├── BroadcastDocumentBuilder.java
│ │ │ │ │ │ ├── DocInfo.java
│ │ │ │ │ │ ├── DocumentAccessAuthentication.java
│ │ │ │ │ │ ├── DocumentAnalyseKit.java
│ │ │ │ │ │ ├── DocumentGenerate.java
│ │ │ │ │ │ ├── DocumentMethod.java
│ │ │ │ │ │ ├── ErrorCodeDoc.java
│ │ │ │ │ │ ├── ErrorCodeDocs.java
│ │ │ │ │ │ ├── ErrorCodeDocsRegion.java
│ │ │ │ │ │ ├── ErrorCodeDocument.java
│ │ │ │ │ │ ├── IoGameDocument.java
│ │ │ │ │ │ ├── IoGameDocumentHelper.java
│ │ │ │ │ │ ├── JavaClassDocInfo.java
│ │ │ │ │ │ ├── TextDocumentGenerate.java
│ │ │ │ │ │ ├── TypeMappingDocument.java
│ │ │ │ │ │ ├── TypeMappingRecord.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── enhance/
│ │ │ │ │ │ ├── BarSkeletonBuilderEnhance.java
│ │ │ │ │ │ └── BarSkeletonBuilderEnhances.java
│ │ │ │ │ ├── exception/
│ │ │ │ │ │ ├── ActionErrorEnum.java
│ │ │ │ │ │ ├── MsgException.java
│ │ │ │ │ │ ├── MsgExceptionInfo.java
│ │ │ │ │ │ ├── MsgExceptionKit.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── flow/
│ │ │ │ │ │ ├── ActionAfter.java
│ │ │ │ │ │ ├── ActionMethodExceptionProcess.java
│ │ │ │ │ │ ├── ActionMethodInOut.java
│ │ │ │ │ │ ├── ActionMethodInvoke.java
│ │ │ │ │ │ ├── ActionMethodParamParser.java
│ │ │ │ │ │ ├── ActionMethodResultWrap.java
│ │ │ │ │ │ ├── FlowContext.java
│ │ │ │ │ │ ├── FlowContextFactory.java
│ │ │ │ │ │ ├── FlowContextKit.java
│ │ │ │ │ │ ├── InternalAboutFlowContext.java
│ │ │ │ │ │ ├── ResponseMessageCreate.java
│ │ │ │ │ │ ├── UserAttachment.java
│ │ │ │ │ │ ├── attr/
│ │ │ │ │ │ │ ├── FlowAttr.java
│ │ │ │ │ │ │ ├── FlowOption.java
│ │ │ │ │ │ │ ├── FlowOptionDynamic.java
│ │ │ │ │ │ │ └── package-info.java
│ │ │ │ │ │ ├── internal/
│ │ │ │ │ │ │ ├── DebugInOut.java
│ │ │ │ │ │ │ ├── DefaultActionAfter.java
│ │ │ │ │ │ │ ├── DefaultActionMethodExceptionProcess.java
│ │ │ │ │ │ │ ├── DefaultActionMethodInvoke.java
│ │ │ │ │ │ │ ├── DefaultActionMethodParamParser.java
│ │ │ │ │ │ │ ├── DefaultActionMethodResultWrap.java
│ │ │ │ │ │ │ ├── DefaultResponseMessageCreate.java
│ │ │ │ │ │ │ ├── StatActionInOut.java
│ │ │ │ │ │ │ ├── ThreadMonitorInOut.java
│ │ │ │ │ │ │ ├── TimeRangeInOut.java
│ │ │ │ │ │ │ ├── TraceIdInOut.java
│ │ │ │ │ │ │ └── package-info.java
│ │ │ │ │ │ ├── package-info.java
│ │ │ │ │ │ └── parser/
│ │ │ │ │ │ ├── BoolValueMethodParser.java
│ │ │ │ │ │ ├── DefaultMethodParser.java
│ │ │ │ │ │ ├── IntValueMethodParser.java
│ │ │ │ │ │ ├── LongValueMethodParser.java
│ │ │ │ │ │ ├── MethodParser.java
│ │ │ │ │ │ ├── MethodParsers.java
│ │ │ │ │ │ ├── StringValueMethodParser.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── package-info.java
│ │ │ │ │ └── runner/
│ │ │ │ │ ├── InternalRunner.java
│ │ │ │ │ ├── Runner.java
│ │ │ │ │ ├── Runners.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── eventbus/
│ │ │ │ │ ├── AbstractEventBusRunner.java
│ │ │ │ │ ├── EventBrokerClientMessage.java
│ │ │ │ │ ├── EventBus.java
│ │ │ │ │ ├── EventBusFireType.java
│ │ │ │ │ ├── EventBusListener.java
│ │ │ │ │ ├── EventBusMessage.java
│ │ │ │ │ ├── EventBusMessageCreator.java
│ │ │ │ │ ├── EventBusRegion.java
│ │ │ │ │ ├── EventBusRunner.java
│ │ │ │ │ ├── EventBusSubscriber.java
│ │ │ │ │ ├── EventSubscribe.java
│ │ │ │ │ ├── EventTopicMessage.java
│ │ │ │ │ ├── ExecutorSelector.java
│ │ │ │ │ ├── InternalAboutAny.java
│ │ │ │ │ ├── InternalAboutEventBus.java
│ │ │ │ │ ├── SubscribeExecutorStrategy.java
│ │ │ │ │ ├── Subscriber.java
│ │ │ │ │ ├── SubscriberInvoke.java
│ │ │ │ │ ├── SubscriberInvokeCreator.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── ext/
│ │ │ │ │ └── spring/
│ │ │ │ │ ├── ActionFactoryBeanForSpring.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── Bundle.java
│ │ │ │ │ └── MessageKey.java
│ │ │ │ ├── kit/
│ │ │ │ │ ├── ExecutorSelectEnum.java
│ │ │ │ │ ├── ExecutorSelectKit.java
│ │ │ │ │ ├── FixedCmd.java
│ │ │ │ │ ├── LogicServerCreateKit.java
│ │ │ │ │ ├── RangeBroadcast.java
│ │ │ │ │ ├── RangeBroadcaster.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── package-info.java
│ │ │ │ ├── protocol/
│ │ │ │ │ ├── BarMessage.java
│ │ │ │ │ ├── HeadMetadata.java
│ │ │ │ │ ├── RequestMessage.java
│ │ │ │ │ ├── ResponseMessage.java
│ │ │ │ │ ├── collect/
│ │ │ │ │ │ ├── RequestCollectMessage.java
│ │ │ │ │ │ ├── ResponseCollectItemMessage.java
│ │ │ │ │ │ ├── ResponseCollectMessage.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── external/
│ │ │ │ │ │ ├── RequestCollectExternalMessage.java
│ │ │ │ │ │ ├── ResponseCollectExternalItemMessage.java
│ │ │ │ │ │ ├── ResponseCollectExternalMessage.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── login/
│ │ │ │ │ │ ├── SettingUserIdMessage.java
│ │ │ │ │ │ ├── SettingUserIdMessageResponse.java
│ │ │ │ │ │ └── SettingUserIdResult.java
│ │ │ │ │ ├── package-info.java
│ │ │ │ │ ├── processor/
│ │ │ │ │ │ ├── EndPointLogicServerMessage.java
│ │ │ │ │ │ ├── EndPointOperationEnum.java
│ │ │ │ │ │ ├── SimpleServerInfo.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ └── wrapper/
│ │ │ │ │ ├── BoolValue.java
│ │ │ │ │ ├── BoolValueList.java
│ │ │ │ │ ├── ByteValueList.java
│ │ │ │ │ ├── IntValue.java
│ │ │ │ │ ├── IntValueList.java
│ │ │ │ │ ├── LongValue.java
│ │ │ │ │ ├── LongValueList.java
│ │ │ │ │ ├── StringValue.java
│ │ │ │ │ ├── StringValueList.java
│ │ │ │ │ ├── ValueRecord.java
│ │ │ │ │ ├── WrapperKit.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── pulse/
│ │ │ │ │ ├── Pulses.java
│ │ │ │ │ ├── core/
│ │ │ │ │ │ ├── PulseChannel.java
│ │ │ │ │ │ ├── PulseTransmit.java
│ │ │ │ │ │ ├── consumer/
│ │ │ │ │ │ │ ├── DefaultPulseConsumers.java
│ │ │ │ │ │ │ ├── PulseConsumer.java
│ │ │ │ │ │ │ ├── PulseConsumers.java
│ │ │ │ │ │ │ └── PulseSignalRequestAccept.java
│ │ │ │ │ │ └── producer/
│ │ │ │ │ │ ├── DefaultPulseProducers.java
│ │ │ │ │ │ ├── PulseCreatePeriod.java
│ │ │ │ │ │ ├── PulseCreateRequest.java
│ │ │ │ │ │ ├── PulseProducer.java
│ │ │ │ │ │ ├── PulseProducers.java
│ │ │ │ │ │ └── PulseSignalResponseAccept.java
│ │ │ │ │ ├── message/
│ │ │ │ │ │ ├── EmptyMessage.java
│ │ │ │ │ │ ├── PulseSignalMessage.java
│ │ │ │ │ │ ├── PulseSignalRequest.java
│ │ │ │ │ │ ├── PulseSignalResponse.java
│ │ │ │ │ │ └── SignalType.java
│ │ │ │ │ ├── package-info.java
│ │ │ │ │ └── runner/
│ │ │ │ │ ├── CreatePulsesRunner.java
│ │ │ │ │ └── PulseRunner.java
│ │ │ │ └── toy/
│ │ │ │ ├── BannerColorStrategy.java
│ │ │ │ ├── BannerData.java
│ │ │ │ ├── BreakingNewsAbout.java
│ │ │ │ ├── InternalMemory.java
│ │ │ │ ├── IoGameBanner.java
│ │ │ │ ├── ToyLine.java
│ │ │ │ ├── ToyTable.java
│ │ │ │ ├── ToyTableRegion.java
│ │ │ │ └── ToyTableRender.java
│ │ │ └── resources/
│ │ │ ├── iohao.properties
│ │ │ └── iohao_zh_CN.properties
│ │ └── test/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── iohao/
│ │ │ └── game/
│ │ │ ├── action/
│ │ │ │ └── skeleton/
│ │ │ │ ├── core/
│ │ │ │ │ ├── ActionParserListenerTest.java
│ │ │ │ │ ├── BarSkeletonTest.java
│ │ │ │ │ ├── DataCodecKitTest.java
│ │ │ │ │ ├── InOutManagerTest.java
│ │ │ │ │ ├── JSR380Test.java
│ │ │ │ │ ├── JSR380ValidatedGroupTest.java
│ │ │ │ │ ├── SimpleWrapperActionTest.java
│ │ │ │ │ ├── WrapperIntTest.java
│ │ │ │ │ ├── WrapperLongTest.java
│ │ │ │ │ ├── action/
│ │ │ │ │ │ ├── BeeAction.java
│ │ │ │ │ │ ├── ExampleActionCmd.java
│ │ │ │ │ │ ├── SimpleWrapperAction.java
│ │ │ │ │ │ ├── WrapperIntAction.java
│ │ │ │ │ │ ├── WrapperLongAction.java
│ │ │ │ │ │ ├── group/
│ │ │ │ │ │ │ ├── Create.java
│ │ │ │ │ │ │ └── Update.java
│ │ │ │ │ │ └── pojo/
│ │ │ │ │ │ ├── BeeApple.java
│ │ │ │ │ │ ├── BirdValid.java
│ │ │ │ │ │ ├── DogValid.java
│ │ │ │ │ │ └── Snake.java
│ │ │ │ │ ├── data/
│ │ │ │ │ │ └── TestDataKit.java
│ │ │ │ │ └── flow/
│ │ │ │ │ └── internal/
│ │ │ │ │ ├── StatActionInOutTest.java
│ │ │ │ │ ├── ThreadMonitorInOutTest.java
│ │ │ │ │ └── TimeRangeInOutTest.java
│ │ │ │ ├── eventbus/
│ │ │ │ │ ├── CustomEvent.java
│ │ │ │ │ ├── EventBusTest.java
│ │ │ │ │ ├── MyMessage.java
│ │ │ │ │ └── MyRecord.java
│ │ │ │ ├── i18n/
│ │ │ │ │ └── BundleTest.java
│ │ │ │ ├── protocol/
│ │ │ │ │ ├── ResponseMessageTest.java
│ │ │ │ │ ├── Student.java
│ │ │ │ │ └── wrapper/
│ │ │ │ │ └── WrapperKitTest.java
│ │ │ │ └── toy/
│ │ │ │ └── ToyTableTest.java
│ │ │ └── common/
│ │ │ └── kit/
│ │ │ └── ClassScannerTest.java
│ │ └── resources/
│ │ └── logback.xml
│ ├── common-kit/
│ │ ├── pom.xml
│ │ └── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── common/
│ │ └── kit/
│ │ ├── ProtoKit.java
│ │ ├── asm/
│ │ │ ├── ClassRefInfo.java
│ │ │ ├── ClassRefInfoBuilder.java
│ │ │ ├── ClassRefInfoKit.java
│ │ │ ├── FieldRefInfo.java
│ │ │ ├── MethodRefInfo.java
│ │ │ └── package-info.java
│ │ ├── io/
│ │ │ ├── FileKit.java
│ │ │ ├── ResourceKit.java
│ │ │ └── package-info.java
│ │ └── system/
│ │ ├── InternalSystemPropsKit.java
│ │ ├── OsInfo.java
│ │ └── package-info.java
│ ├── common-micro-kit/
│ │ ├── README.md
│ │ ├── pom.xml
│ │ └── src/
│ │ ├── main/
│ │ │ └── java/
│ │ │ ├── com/
│ │ │ │ └── iohao/
│ │ │ │ └── game/
│ │ │ │ └── common/
│ │ │ │ ├── consts/
│ │ │ │ │ ├── CommonConst.java
│ │ │ │ │ └── IoGameLogName.java
│ │ │ │ ├── internal/
│ │ │ │ │ ├── BootConfig.java
│ │ │ │ │ ├── BootItemConfig.java
│ │ │ │ │ └── BootItemConfigKit.java
│ │ │ │ └── kit/
│ │ │ │ ├── AboutKit.java
│ │ │ │ ├── ArrayKit.java
│ │ │ │ ├── BaseTypeKit.java
│ │ │ │ ├── ByteKit.java
│ │ │ │ ├── ClassScanner.java
│ │ │ │ ├── CollKit.java
│ │ │ │ ├── CompletableFutureKit.java
│ │ │ │ ├── ExecutorKit.java
│ │ │ │ ├── HashKit.java
│ │ │ │ ├── MoreKit.java
│ │ │ │ ├── MurmurHash3.java
│ │ │ │ ├── NetworkKit.java
│ │ │ │ ├── OperationCode.java
│ │ │ │ ├── PresentKit.java
│ │ │ │ ├── RandomKit.java
│ │ │ │ ├── RuntimeKit.java
│ │ │ │ ├── SafeKit.java
│ │ │ │ ├── StrKit.java
│ │ │ │ ├── TimeBetweenKit.java
│ │ │ │ ├── TimeFormatterKit.java
│ │ │ │ ├── TimeKit.java
│ │ │ │ ├── attr/
│ │ │ │ │ ├── AttrOption.java
│ │ │ │ │ ├── AttrOptionDynamic.java
│ │ │ │ │ ├── AttrOptions.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── beans/
│ │ │ │ │ └── property/
│ │ │ │ │ ├── AbstractPropertyValueObservable.java
│ │ │ │ │ ├── BooleanProperty.java
│ │ │ │ │ ├── IntegerProperty.java
│ │ │ │ │ ├── LongProperty.java
│ │ │ │ │ ├── NumberPropertyValueObservable.java
│ │ │ │ │ ├── ObjectProperty.java
│ │ │ │ │ ├── PropertyAbout.java
│ │ │ │ │ ├── PropertyChangeListener.java
│ │ │ │ │ ├── PropertyValueObservable.java
│ │ │ │ │ ├── StringProperty.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── collect/
│ │ │ │ │ ├── ListMultiMap.java
│ │ │ │ │ ├── MultiMap.java
│ │ │ │ │ ├── NonBlockingListMultiMap.java
│ │ │ │ │ ├── NonBlockingSetMultiMap.java
│ │ │ │ │ ├── SetMultiMap.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── concurrent/
│ │ │ │ │ ├── CommonTaskListener.java
│ │ │ │ │ ├── DaemonThreadFactory.java
│ │ │ │ │ ├── FixedNameThreadFactory.java
│ │ │ │ │ ├── IntervalTaskListener.java
│ │ │ │ │ ├── OnceTaskListener.java
│ │ │ │ │ ├── TaskKit.java
│ │ │ │ │ ├── TaskListener.java
│ │ │ │ │ ├── ThreadCreator.java
│ │ │ │ │ ├── executor/
│ │ │ │ │ │ ├── AbstractThreadExecutorRegion.java
│ │ │ │ │ │ ├── ExecutorRegion.java
│ │ │ │ │ │ ├── ExecutorRegionKit.java
│ │ │ │ │ │ ├── SimpleThreadExecutorRegion.java
│ │ │ │ │ │ ├── ThreadExecutor.java
│ │ │ │ │ │ ├── ThreadExecutorRegion.java
│ │ │ │ │ │ ├── UserThreadExecutorRegion.java
│ │ │ │ │ │ ├── UserVirtualThreadExecutorRegion.java
│ │ │ │ │ │ └── package-info.java
│ │ │ │ │ ├── package-info.java
│ │ │ │ │ └── timer/
│ │ │ │ │ └── delay/
│ │ │ │ │ ├── DelayTask.java
│ │ │ │ │ ├── DelayTaskKit.java
│ │ │ │ │ ├── DelayTaskRegion.java
│ │ │ │ │ ├── InternalDelayAbout.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── exception/
│ │ │ │ │ ├── CommonIllegalArgumentException.java
│ │ │ │ │ ├── CommonNullPointerException.java
│ │ │ │ │ ├── CommonRuntimeException.java
│ │ │ │ │ ├── ThrowKit.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── id/
│ │ │ │ │ ├── CacheKeyKit.java
│ │ │ │ │ ├── IdKit.java
│ │ │ │ │ ├── StringIdSupplier.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── micro/
│ │ │ │ │ └── room/
│ │ │ │ │ ├── MicroRoom.java
│ │ │ │ │ ├── MicroRooms.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── package-info.java
│ │ │ │ ├── time/
│ │ │ │ │ ├── CacheTimeKit.java
│ │ │ │ │ ├── ConfigTimeKit.java
│ │ │ │ │ ├── ExpireTimeKit.java
│ │ │ │ │ ├── FormatTimeKit.java
│ │ │ │ │ ├── ToTimeKit.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── trace/
│ │ │ │ │ ├── TraceIdSupplier.java
│ │ │ │ │ ├── TraceKit.java
│ │ │ │ │ └── package-info.java
│ │ │ │ └── weight/
│ │ │ │ ├── Weight.java
│ │ │ │ ├── WeightKit.java
│ │ │ │ └── package-info.java
│ │ │ └── module-info.java.txt
│ │ └── test/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── iohao/
│ │ │ └── game/
│ │ │ └── common/
│ │ │ └── kit/
│ │ │ ├── ByteKitTest.java
│ │ │ ├── TimeKitTest.java
│ │ │ ├── attr/
│ │ │ │ └── AttrOptionDynamicTest.java
│ │ │ ├── beans/
│ │ │ │ └── property/
│ │ │ │ └── PropertyValueObservableTest.java
│ │ │ ├── collect/
│ │ │ │ ├── ListMultiMapTest.java
│ │ │ │ └── SetMultiMapTest.java
│ │ │ ├── concurrent/
│ │ │ │ ├── TaskKitTest.java
│ │ │ │ ├── executor/
│ │ │ │ │ └── ExecutorRegionKitTest.java
│ │ │ │ └── timer/
│ │ │ │ └── delay/
│ │ │ │ └── DelayTaskTest.java
│ │ │ ├── time/
│ │ │ │ ├── CacheTimeKitTest.java
│ │ │ │ ├── ExpireTimeKitTest.java
│ │ │ │ └── FormatTimeKitTest.java
│ │ │ └── trace/
│ │ │ └── TraceKitTest.java
│ │ └── resources/
│ │ └── logback.xml
│ └── common-validation/
│ ├── README.md
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── common/
│ │ └── validation/
│ │ ├── Validation.java
│ │ ├── Validator.java
│ │ ├── annotation/
│ │ │ ├── EnableValidation.java
│ │ │ └── package-info.java
│ │ ├── package-info.java
│ │ ├── processor/
│ │ │ ├── ValidationProcessor.java
│ │ │ └── package-info.java
│ │ └── support/
│ │ ├── JakartaValidator.java
│ │ ├── JavaxValidator.java
│ │ └── package-info.java
│ └── resources/
│ └── META-INF/
│ ├── gradle/
│ │ └── incremental.annotation.processors
│ └── services/
│ └── javax.annotation.processing.Processor
├── doc_maven.txt
├── external/
│ ├── README.md
│ ├── external-core/
│ │ ├── pom.xml
│ │ └── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── external/
│ │ └── core/
│ │ ├── ExternalCore.java
│ │ ├── ExternalCoreSetting.java
│ │ ├── ExternalServer.java
│ │ ├── aware/
│ │ │ ├── ExternalCoreSettingAware.java
│ │ │ ├── UserSessionsAware.java
│ │ │ └── package-info.java
│ │ ├── broker/
│ │ │ └── client/
│ │ │ ├── ExternalBrokerClientStartup.java
│ │ │ ├── enhance/
│ │ │ │ ├── ExternalEnhance.java
│ │ │ │ └── ExternalEnhances.java
│ │ │ ├── ext/
│ │ │ │ ├── ExternalBizRegion.java
│ │ │ │ ├── ExternalBizRegionContext.java
│ │ │ │ ├── ExternalBizRegions.java
│ │ │ │ ├── impl/
│ │ │ │ │ ├── AttachmentExternalBizRegion.java
│ │ │ │ │ ├── ExistUserExternalBizRegion.java
│ │ │ │ │ ├── ExternalBizRegionKit.java
│ │ │ │ │ ├── ForcedOfflineExternalBizRegion.java
│ │ │ │ │ ├── UserHeadMetadataExternalBizRegion.java
│ │ │ │ │ └── package-info.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ └── processor/
│ │ │ ├── BroadcastMessageExternalProcessor.java
│ │ │ ├── BroadcastOrderMessageExternalProcessor.java
│ │ │ ├── BrokerClientOfflineMessageExternalProcessor.java
│ │ │ ├── BrokerClientOnlineMessageExternalProcessor.java
│ │ │ ├── EndPointLogicServerMessageExternalProcessor.java
│ │ │ ├── RequestCollectExternalMessageExternalProcessor.java
│ │ │ ├── ResponseMessageExternalProcessor.java
│ │ │ ├── SettingUserIdMessageExternalProcessor.java
│ │ │ ├── listener/
│ │ │ │ ├── CmdRegionBrokerClientListener.java
│ │ │ │ └── package-info.java
│ │ │ └── package-info.java
│ │ ├── config/
│ │ │ ├── ExternalGlobalConfig.java
│ │ │ ├── ExternalJoinEnum.java
│ │ │ └── package-info.java
│ │ ├── hook/
│ │ │ ├── AccessAuthenticationHook.java
│ │ │ ├── BrokerClientExternalAttr.java
│ │ │ ├── IdleHook.java
│ │ │ ├── UserHook.java
│ │ │ ├── cache/
│ │ │ │ ├── CmdCacheOption.java
│ │ │ │ ├── ExternalCmdCache.java
│ │ │ │ ├── ExternalCmdCacheSetting.java
│ │ │ │ ├── InternalAboutCache.java
│ │ │ │ ├── internal/
│ │ │ │ │ └── DefaultExternalCmdCache.java
│ │ │ │ └── package-info.java
│ │ │ ├── internal/
│ │ │ │ ├── DefaultAccessAuthenticationHook.java
│ │ │ │ ├── DefaultUserHook.java
│ │ │ │ ├── IdleProcessSetting.java
│ │ │ │ └── package-info.java
│ │ │ └── package-info.java
│ │ ├── kit/
│ │ │ ├── ExternalKit.java
│ │ │ └── package-info.java
│ │ ├── message/
│ │ │ ├── DefaultExternalCodec.java
│ │ │ ├── ExternalCodec.java
│ │ │ ├── ExternalCodecKit.java
│ │ │ ├── ExternalMessage.java
│ │ │ ├── ExternalMessageCmdCode.java
│ │ │ └── package-info.java
│ │ ├── micro/
│ │ │ ├── MicroBootstrap.java
│ │ │ ├── MicroBootstrapFlow.java
│ │ │ ├── PipelineContext.java
│ │ │ ├── join/
│ │ │ │ ├── ExternalJoinSelector.java
│ │ │ │ ├── ExternalJoinSelectors.java
│ │ │ │ └── package-info.java
│ │ │ └── package-info.java
│ │ ├── package-info.java
│ │ └── session/
│ │ ├── UserChannelId.java
│ │ ├── UserSession.java
│ │ ├── UserSessionOption.java
│ │ ├── UserSessionState.java
│ │ ├── UserSessions.java
│ │ └── package-info.java
│ └── external-netty/
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── external/
│ │ └── core/
│ │ └── netty/
│ │ ├── DefaultExternalCore.java
│ │ ├── DefaultExternalCoreSetting.java
│ │ ├── DefaultExternalServer.java
│ │ ├── DefaultExternalServerBuilder.java
│ │ ├── SettingOption.java
│ │ ├── handler/
│ │ │ ├── CmdCacheHandler.java
│ │ │ ├── CmdCheckHandler.java
│ │ │ ├── SimpleLoggerHandler.java
│ │ │ ├── SocketCmdAccessAuthHandler.java
│ │ │ ├── SocketIdleExcludeHandler.java
│ │ │ ├── SocketIdleHandler.java
│ │ │ ├── SocketRequestBrokerHandler.java
│ │ │ ├── SocketUserSessionHandler.java
│ │ │ ├── check/
│ │ │ │ ├── HttpFallbackHandler.java
│ │ │ │ └── TcpProtocolSanityCheckHandler.java
│ │ │ ├── codec/
│ │ │ │ ├── TcpExternalCodec.java
│ │ │ │ └── WebSocketExternalCodec.java
│ │ │ └── ws/
│ │ │ ├── HttpRealIpHandler.java
│ │ │ └── WebSocketVerifyHandler.java
│ │ ├── hook/
│ │ │ ├── DefaultSocketIdleHook.java
│ │ │ └── SocketIdleHook.java
│ │ ├── kit/
│ │ │ └── ExternalServerCreateKit.java
│ │ ├── micro/
│ │ │ ├── AbstractMicroBootstrap.java
│ │ │ ├── AbstractMicroBootstrapFlow.java
│ │ │ ├── DefaultPipelineContext.java
│ │ │ ├── SocketMicroBootstrap.java
│ │ │ ├── SocketMicroBootstrapFlow.java
│ │ │ ├── TcpMicroBootstrapFlow.java
│ │ │ ├── WebSocketMicroBootstrapFlow.java
│ │ │ ├── auto/
│ │ │ │ ├── EventLoopGroupThreadFactory.java
│ │ │ │ ├── GroupChannelOption.java
│ │ │ │ ├── GroupChannelOptionForLinux.java
│ │ │ │ ├── GroupChannelOptionForMac.java
│ │ │ │ └── GroupChannelOptionForOther.java
│ │ │ └── join/
│ │ │ ├── SocketExternalJoinSelector.java
│ │ │ ├── TcpExternalJoinSelector.java
│ │ │ └── WebSocketExternalJoinSelector.java
│ │ ├── package-info.java
│ │ └── session/
│ │ ├── AbstractUserSession.java
│ │ ├── AbstractUserSessions.java
│ │ ├── SocketUserSession.java
│ │ └── SocketUserSessions.java
│ └── resources/
│ └── META-INF/
│ └── services/
│ └── com.iohao.game.external.core.micro.join.ExternalJoinSelector
├── history/
│ └── changeLog_ioGame17.md
├── net-bolt/
│ ├── README.md
│ ├── bolt-broker-server/
│ │ ├── README.md
│ │ ├── pom.xml
│ │ └── src/
│ │ ├── main/
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── iohao/
│ │ │ └── game/
│ │ │ └── bolt/
│ │ │ └── broker/
│ │ │ ├── cluster/
│ │ │ │ ├── Broker.java
│ │ │ │ ├── BrokerCluster.java
│ │ │ │ ├── BrokerClusterManager.java
│ │ │ │ ├── BrokerClusterManagerBuilder.java
│ │ │ │ ├── BrokerClusterMessageHandler.java
│ │ │ │ ├── BrokerClusterMetadata.java
│ │ │ │ ├── BrokerRunModeEnum.java
│ │ │ │ ├── ClusterMessageListener.java
│ │ │ │ └── package-info.java
│ │ │ └── server/
│ │ │ ├── BrokerServer.java
│ │ │ ├── BrokerServerBuilder.java
│ │ │ ├── aware/
│ │ │ │ ├── BrokerClientModulesAware.java
│ │ │ │ └── BrokerServerAware.java
│ │ │ ├── balanced/
│ │ │ │ ├── BalancedManager.java
│ │ │ │ ├── BrokerClientLoadBalanced.java
│ │ │ │ ├── ExternalBrokerClientLoadBalanced.java
│ │ │ │ ├── LogicBrokerClientLoadBalanced.java
│ │ │ │ └── region/
│ │ │ │ ├── BrokerClientProxy.java
│ │ │ │ ├── BrokerClientRegion.java
│ │ │ │ ├── BrokerClientRegionFactory.java
│ │ │ │ ├── DefaultBrokerClientRegion.java
│ │ │ │ ├── DefaultWithElementSelector.java
│ │ │ │ ├── StrictBrokerClientRegion.java
│ │ │ │ └── WithElementSelector.java
│ │ │ ├── cluster/
│ │ │ │ └── ClusterMessageListenerImpl.java
│ │ │ ├── enhance/
│ │ │ │ ├── BrokerEnhance.java
│ │ │ │ └── BrokerEnhances.java
│ │ │ ├── kit/
│ │ │ │ ├── BrokerPrintKit.java
│ │ │ │ └── EndPointClientIdKit.java
│ │ │ ├── processor/
│ │ │ │ ├── BroadcastMessageBrokerProcessor.java
│ │ │ │ ├── BroadcastOrderMessageBrokerProcessor.java
│ │ │ │ ├── BrokerClientItemConnectMessageBrokerProcessor.java
│ │ │ │ ├── BrokerExternalKit.java
│ │ │ │ ├── ConnectionCloseEventBrokerProcessor.java
│ │ │ │ ├── ConnectionEventBrokerProcessor.java
│ │ │ │ ├── ConnectionExceptionEventBrokerProcessor.java
│ │ │ │ ├── ConnectionFailedEventBrokerProcessor.java
│ │ │ │ ├── EndPointLogicServerMessageBrokerProcessor.java
│ │ │ │ ├── EventBusMessageBrokerProcessor.java
│ │ │ │ ├── InnerModuleMessageBrokerProcessor.java
│ │ │ │ ├── InnerModuleRequestCollectExternalMessageBrokerProcessor.java
│ │ │ │ ├── InnerModuleRequestCollectMessageBrokerProcessor.java
│ │ │ │ ├── InnerModuleVoidMessageBrokerProcessor.java
│ │ │ │ ├── LineKit.java
│ │ │ │ ├── PulseSignalRequestBrokerProcessor.java
│ │ │ │ ├── PulseSignalResponseBrokerProcessor.java
│ │ │ │ ├── RegisterBrokerClientModuleMessageBrokerProcessor.java
│ │ │ │ ├── RequestMessageBrokerProcessor.java
│ │ │ │ ├── ResponseMessageBrokerProcessor.java
│ │ │ │ └── SettingUserIdMessageBrokerProcessor.java
│ │ │ └── service/
│ │ │ ├── BrokerClientModules.java
│ │ │ └── DefaultBrokerClientModules.java
│ │ └── test/
│ │ └── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── bolt/
│ │ └── broker/
│ │ ├── cluster/
│ │ │ ├── Gossip1Test.java
│ │ │ └── Gossip2Test.java
│ │ └── server/
│ │ └── BrokerServerStandaloneTest.java
│ ├── bolt-client/
│ │ ├── pom.xml
│ │ └── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── bolt/
│ │ └── broker/
│ │ └── client/
│ │ ├── AbstractBrokerClientStartup.java
│ │ ├── BrokerClientApplication.java
│ │ ├── BrokerClientStartup.java
│ │ ├── RemoteAddress.java
│ │ ├── package-info.java
│ │ └── processor/
│ │ ├── BoltChannelContext.java
│ │ ├── BrokerClientLineKit.java
│ │ ├── BrokerClientOfflineMessageLogicProcessor.java
│ │ ├── BrokerClientOnlineMessageLogicProcessor.java
│ │ ├── BrokerClusterMessageClientProcessor.java
│ │ ├── EventBusMessageClientProcessor.java
│ │ ├── RequestBrokerClientModuleMessageClientProcessor.java
│ │ ├── RequestMessageClientProcessor.java
│ │ ├── connection/
│ │ │ ├── CloseConnectEventClientProcessor.java
│ │ │ ├── ConnectEventClientProcessor.java
│ │ │ ├── ConnectFailedEventClientProcessor.java
│ │ │ ├── ExceptionConnectEventClientProcessor.java
│ │ │ └── package-info.java
│ │ └── package-info.java
│ └── bolt-core/
│ ├── pom.xml
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ ├── alipay/
│ │ └── sofa/
│ │ └── common/
│ │ └── log/
│ │ └── factory/
│ │ └── LoggerSpaceFactory4LogbackBuilder.java
│ └── iohao/
│ └── game/
│ ├── bolt/
│ │ └── broker/
│ │ ├── client/
│ │ │ └── kit/
│ │ │ ├── ExternalCommunicationKit.java
│ │ │ ├── UserIdSettingKit.java
│ │ │ └── package-info.java
│ │ └── core/
│ │ ├── BoltConnection.java
│ │ ├── GroupWith.java
│ │ ├── aware/
│ │ │ ├── AwareInject.java
│ │ │ ├── AwareKit.java
│ │ │ ├── BrokerClientAware.java
│ │ │ ├── BrokerClientItemAware.java
│ │ │ ├── CmdRegionsAware.java
│ │ │ ├── PulseConsumerAware.java
│ │ │ ├── PulseProducerAware.java
│ │ │ ├── UserProcessorExecutorAware.java
│ │ │ ├── UserProcessorExecutorSelectorAware.java
│ │ │ └── UserProcessorInNettyThreadAware.java
│ │ ├── client/
│ │ │ ├── Broadcast.java
│ │ │ ├── BroadcastDebug.java
│ │ │ ├── BrokerAddress.java
│ │ │ ├── BrokerClient.java
│ │ │ ├── BrokerClientAttr.java
│ │ │ ├── BrokerClientBuilder.java
│ │ │ ├── BrokerClientHelper.java
│ │ │ ├── BrokerClientItem.java
│ │ │ ├── BrokerClientManager.java
│ │ │ ├── BrokerClientType.java
│ │ │ ├── BrokerClients.java
│ │ │ ├── DefaultProcessorContext.java
│ │ │ ├── EventBusBrokerClientListener.java
│ │ │ └── config/
│ │ │ └── BrokerClientStatusConfig.java
│ │ ├── common/
│ │ │ ├── AbstractAsyncUserProcessor.java
│ │ │ ├── DefaultUserProcessorExecutorSelectorStrategy.java
│ │ │ ├── DefaultUserProcessorExecutorStrategy.java
│ │ │ ├── IoGameGlobalConfig.java
│ │ │ ├── ProcessorSelectorThreadExecutorRegion.java
│ │ │ ├── UserProcessorExecutorSelectorStrategy.java
│ │ │ ├── UserProcessorExecutorStrategy.java
│ │ │ ├── VirtualThreadUserProcessorExecutorStrategy.java
│ │ │ └── processor/
│ │ │ ├── hook/
│ │ │ │ ├── ClientProcessorHooks.java
│ │ │ │ ├── DefaultRequestMessageClientProcessorHook.java
│ │ │ │ └── RequestMessageClientProcessorHook.java
│ │ │ ├── listener/
│ │ │ │ ├── BrokerClientListener.java
│ │ │ │ ├── BrokerClientListenerRegion.java
│ │ │ │ ├── ConnectionBeforeListener.java
│ │ │ │ ├── LineListener.java
│ │ │ │ └── SimplePrintBrokerClientListener.java
│ │ │ └── pulse/
│ │ │ ├── PulseSignalRequestUserProcessor.java
│ │ │ └── PulseSignalResponseUserProcessor.java
│ │ ├── kit/
│ │ │ └── HessianKit.java
│ │ ├── loadbalance/
│ │ │ ├── ElementSelector.java
│ │ │ ├── ElementSelectorFactory.java
│ │ │ ├── RandomElementSelector.java
│ │ │ └── RingElementSelector.java
│ │ └── message/
│ │ ├── BroadcastMessage.java
│ │ ├── BroadcastOrderMessage.java
│ │ ├── BrokerClientItemConnectMessage.java
│ │ ├── BrokerClientModuleMessage.java
│ │ ├── BrokerClientOfflineMessage.java
│ │ ├── BrokerClientOnlineMessage.java
│ │ ├── BrokerClusterMessage.java
│ │ ├── BrokerMessage.java
│ │ ├── InnerModuleMessage.java
│ │ ├── InnerModuleVoidMessage.java
│ │ └── RequestBrokerClientModuleMessage.java
│ └── core/
│ └── common/
│ ├── NetCommonKit.java
│ ├── client/
│ │ ├── Attachment.java
│ │ └── ExternalBizCodeCont.java
│ └── cmd/
│ ├── BrokerClientId.java
│ ├── CmdRegion.java
│ ├── CmdRegions.java
│ ├── DefaultCmdRegion.java
│ └── DefaultCmdRegions.java
├── pom.xml
├── run-one/
│ └── run-one-netty/
│ ├── pom.xml
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── iohao/
│ └── game/
│ └── external/
│ └── core/
│ └── netty/
│ └── simple/
│ ├── InternalRunOne.java
│ ├── NettyClusterSimpleHelper.java
│ ├── NettyClusterSimpleRunOne.java
│ ├── NettyRunOne.java
│ └── NettySimpleHelper.java
└── widget/
├── generate-code/
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── action/
│ │ └── skeleton/
│ │ └── core/
│ │ └── doc/
│ │ ├── CsharpDocumentGenerate.java
│ │ ├── DocumentGenerateAbout.java
│ │ ├── GDScriptDocumentGenerate.java
│ │ └── TypeScriptDocumentGenerate.java
│ └── resources/
│ └── generate/
│ ├── csharp/
│ │ ├── action.txt
│ │ ├── action_method.txt
│ │ ├── action_method_no_param.txt
│ │ ├── action_method_result_example.txt
│ │ ├── action_method_void.txt
│ │ ├── action_method_void_no_param.txt
│ │ ├── broadcast_action.txt
│ │ ├── broadcast_action_example.txt
│ │ ├── broadcast_action_example_action.txt
│ │ └── game_code.txt
│ ├── gdscript/
│ │ ├── action.txt
│ │ ├── action_method.txt
│ │ ├── action_method_no_param.txt
│ │ ├── action_method_result_example.txt
│ │ ├── action_method_void.txt
│ │ ├── action_method_void_no_param.txt
│ │ ├── broadcast_action.txt
│ │ ├── broadcast_action_example.txt
│ │ ├── broadcast_action_example_action.txt
│ │ └── game_code.txt
│ └── ts/
│ ├── action.txt
│ ├── action_method.txt
│ ├── action_method_no_param.txt
│ ├── action_method_result_example.txt
│ ├── action_method_void.txt
│ ├── action_method_void_no_param.txt
│ ├── broadcast_action.txt
│ ├── broadcast_action_example.txt
│ ├── broadcast_action_example_action.txt
│ └── game_code.txt
├── light-client/
│ ├── pom.xml
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── iohao/
│ └── game/
│ └── external/
│ └── client/
│ ├── AbstractInputCommandRegion.java
│ ├── ClientConnectOption.java
│ ├── InputCommandCreate.java
│ ├── InputCommandRegion.java
│ ├── command/
│ │ ├── CallbackDelegate.java
│ │ ├── CommandResult.java
│ │ ├── InputCommand.java
│ │ ├── ListenCommand.java
│ │ ├── RequestCommand.java
│ │ └── RequestDataDelegate.java
│ ├── join/
│ │ ├── ClientConnect.java
│ │ ├── ClientConnects.java
│ │ ├── ClientRunOne.java
│ │ ├── ClientTcpExternalCodec.java
│ │ ├── TcpClientStartup.java
│ │ ├── WebSocketClientStartup.java
│ │ └── handler/
│ │ └── ClientMessageHandler.java
│ ├── kit/
│ │ ├── AssertKit.java
│ │ ├── ClientKit.java
│ │ ├── ClientUserConfigs.java
│ │ ├── ScannerKit.java
│ │ └── SplitParam.java
│ ├── package-info.java
│ └── user/
│ ├── ClientChannelRead.java
│ ├── ClientUser.java
│ ├── ClientUserChannel.java
│ ├── ClientUserInputCommands.java
│ ├── ClientUsers.java
│ ├── DefaultClientUser.java
│ └── InternalAboutClient.java
├── light-domain-event/
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── widget/
│ │ └── light/
│ │ └── domain/
│ │ └── event/
│ │ ├── DisruptorManager.java
│ │ ├── DomainEventContext.java
│ │ ├── DomainEventContextParam.java
│ │ ├── DomainEventPublish.java
│ │ ├── annotation/
│ │ │ └── DomainEvent.java
│ │ ├── disruptor/
│ │ │ ├── ConsumeEventHandler.java
│ │ │ ├── DefaultDisruptorCreate.java
│ │ │ ├── DisruptorCreate.java
│ │ │ ├── DomainEventSource.java
│ │ │ ├── EventDisruptor.java
│ │ │ └── package-info.java
│ │ ├── exception/
│ │ │ ├── DefaultDomainEventExceptionHandler.java
│ │ │ └── package-info.java
│ │ ├── message/
│ │ │ ├── DomainEventHandler.java
│ │ │ ├── Eo.java
│ │ │ ├── Topic.java
│ │ │ └── package-info.java
│ │ └── package-info.java
│ └── test/
│ └── java/
│ └── com/
│ └── iohao/
│ └── game/
│ └── widget/
│ └── light/
│ └── domain/
│ └── event/
│ ├── StudentDomainEventTest.java
│ ├── StudentDomainEventTest2.java
│ ├── UserLoginDomainEventTest.java
│ ├── student/
│ │ ├── StudentCountEventHandler.java
│ │ ├── StudentEmailEventHandler1.java
│ │ ├── StudentEo.java
│ │ ├── StudentGoHomeEventHandler2.java
│ │ ├── StudentSleepEventHandler3.java
│ │ └── package-info.java
│ └── user/
│ ├── UserLogin.java
│ ├── UserLoginEmailEventHandler.java
│ └── package-info.java
├── light-game-room/
│ ├── pom.xml
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── iohao/
│ └── game/
│ └── widget/
│ └── light/
│ └── room/
│ ├── GameRoomService.java
│ ├── Player.java
│ ├── Room.java
│ ├── RoomBroadcastEnhance.java
│ ├── RoomKit.java
│ ├── RoomService.java
│ ├── RoomStatusEnum.java
│ ├── SimplePlayer.java
│ ├── SimpleRoom.java
│ ├── SimpleRoomService.java
│ ├── domain/
│ │ ├── GameFlowEo.java
│ │ ├── GameFlowEventHandler.java
│ │ ├── OperationContextEventHandler.java
│ │ └── package-info.java
│ ├── flow/
│ │ ├── GameFixedService.java
│ │ ├── GameFlowContext.java
│ │ ├── GameFlowService.java
│ │ ├── GameStartService.java
│ │ ├── PlayerCreator.java
│ │ ├── RoomCreateContext.java
│ │ ├── RoomCreator.java
│ │ ├── SimpleGameFlowContext.java
│ │ ├── SimpleRoomCreateContext.java
│ │ └── package-info.java
│ ├── operation/
│ │ ├── OperationContext.java
│ │ ├── OperationFactory.java
│ │ ├── OperationHandler.java
│ │ ├── OperationService.java
│ │ ├── PlayerOperationContext.java
│ │ ├── SimpleOperationFactory.java
│ │ ├── SimpleOperationHandler.java
│ │ └── package-info.java
│ └── package-info.java
├── light-jprotobuf/
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── widget/
│ │ └── light/
│ │ └── protobuf/
│ │ ├── FieldNameGenerate.java
│ │ ├── ProtoFieldTypeHolder.java
│ │ ├── ProtoFileMerge.java
│ │ ├── ProtoFileValue.java
│ │ ├── ProtoGenerateFile.java
│ │ ├── ProtoGenerateSetting.java
│ │ ├── ProtoJava.java
│ │ ├── ProtoJavaAnalyse.java
│ │ ├── ProtoJavaField.java
│ │ ├── ProtoJavaRegion.java
│ │ ├── ProtoJavaRegionKey.java
│ │ └── kit/
│ │ └── GenerateFileKit.java
│ └── test/
│ └── java/
│ └── com/
│ └── iohao/
│ └── game/
│ └── widget/
│ └── light/
│ └── protobuf/
│ ├── ProtoJavaTest.java
│ └── data/
│ ├── AnimalTypeEnum.java
│ ├── Cat.java
│ ├── Food.java
│ ├── ProtoTeacher.java
│ ├── TempProtoFile.java
│ ├── TestEnum.java
│ └── Tiger.java
├── light-profile/
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── widget/
│ │ └── light/
│ │ └── profile/
│ │ ├── Profile.java
│ │ ├── ProfileManager.java
│ │ ├── ResourcePatternResolverProfile.java
│ │ └── package-info.java
│ └── test/
│ ├── java/
│ │ └── com/
│ │ └── iohao/
│ │ └── game/
│ │ └── widget/
│ │ └── light/
│ │ └── profile/
│ │ ├── ProfileManagerTest.java
│ │ └── ResourcePatternResolverProfileTest.java
│ └── resources/
│ └── conf/
│ ├── common/
│ │ ├── db.props
│ │ └── other.props
│ ├── local/
│ │ └── db.props
│ └── production/
│ ├── db.props
│ └── other.props
└── other-tool/
├── README.md
├── pom.xml
└── src/
└── main/
└── java/
├── com/
│ └── iohao/
│ └── game/
│ └── common/
│ └── kit/
│ └── adapter/
│ ├── AdapterHuUtils.java
│ ├── HuArrayUtil.java
│ ├── HuAssert.java
│ ├── HuBase16Codec.java
│ ├── HuCharFinder.java
│ ├── HuCharUtil.java
│ ├── HuCharsetUtil.java
│ ├── HuClassLoaderUtil.java
│ ├── HuClassPathResource.java
│ ├── HuClassUtil.java
│ ├── HuComputeIter.java
│ ├── HuCopyVisitor.java
│ ├── HuExceptionUtil.java
│ ├── HuFastByteArrayOutputStream.java
│ ├── HuFastByteBuffer.java
│ ├── HuFileResource.java
│ ├── HuFileUtil.java
│ ├── HuFileWriter.java
│ ├── HuFilter.java
│ ├── HuFinder.java
│ ├── HuHexUtil.java
│ ├── HuIoCopier.java
│ ├── HuIoRuntimeException.java
│ ├── HuIoUtil.java
│ ├── HuNoResourceException.java
│ ├── HuObjectUtil.java
│ ├── HuPathUtil.java
│ ├── HuPercentCodec.java
│ ├── HuResource.java
│ ├── HuResourceUtil.java
│ ├── HuRfc3986.java
│ ├── HuSplitIter.java
│ ├── HuStrFinder.java
│ ├── HuStrFormatter.java
│ ├── HuStrUtil.java
│ ├── HuStreamCopier.java
│ ├── HuTextFinder.java
│ ├── HuUrlResource.java
│ ├── HuUrlUtil.java
│ ├── HuUtilException.java
│ └── package-info.java
└── module-info.java.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/ask-question.md
================================================
---
name: 提问与交流
about: 对框架使用过程中遇到的问题、不清楚或模糊的地方;询问框架能否支持某类需求
---
### 你的问题 | 使用场景
描述你遇到的问题,或使用场景(询问框架能否满足此类需求)
### 预期值
期望的预期值
### 实际值
实际值
### 复现步骤
描述复现步骤,并提供复现 demo
### 版本
- ioGame version:
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: bug
about: 疑似 bug
---
### 你的问题
描述你遇到的问题
### 预期值
期望的预期值
### 实际值
实际值
### 复现步骤
描述复现步骤,并提供复现 demo
### 版本
- ioGame version:
================================================
FILE: .github/ISSUE_TEMPLATE/empty-issues.md
================================================
---
name: empty issues
about: 自定义 issues 描述
---
### 提问
================================================
FILE: .github/ISSUE_TEMPLATE/enhance-task.md
================================================
---
name: 功能增强建议
about: 对框架功能增强、新增...等相关建议
---
### 新增功能的使用场景
描述功能的使用场景、作用;或描述功能所带来的好处
================================================
FILE: .gitignore
================================================
### other dir ###
classes/
.temp/
logs/
log/
.svn/
svn/
lib/
.act.*
system/
HELP.md
!**/src/main/**
!**/src/test/**
*.class
### jreble config ###
rebel.xml
### Package Files ###
*.jar
*.war
*.ear
*.zip
# tcp 与前端通讯的 api
tcp-api/
# gradle #
build/
.gradle/
out/
### maven ###
target/
!.mvn/wrapper/maven-wrapper.jar
.mvn
mvnw
mvnw.cmd
### intellj file type ###
bin/
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### STS ###
.apt_generated
.factorypath
.settings
.springBeans
.sts4-cache
### eclipse file type ###
.settings/
.classpath
.project
### ---- Mac OS X ###
.DS_Store
Icon?
### ---- Windows ###
### Windows image file caches ###
Thumbs.db
### Folder integration file ###
Desktop.ini
### ---- Javadoc ###
docs.tar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/target/
doc_game.txt
.claude
================================================
FILE: BACKERS.md
================================================
#### 支持名单
通过 wx、alipay 支付时,不能看见全称,下面使用 '-' 来代替 *。
#### 2025
##### 小星星
| 称号 | 名单 |
| ---- | ------------------------------------------------ |
| ⭐⭐ | wuhudsm(admin@playdnf.com)、65714050@qq.com、M-x |
##### 感谢支持
ZhangSir、-鸟
---
#### 2024
##### 小星星
| 称号 | 名单 |
| ------------- | -------------------------------- |
| ⭐⭐ | 刘先生、wells974、404 Not Found(342644552)、山里人(higaojun@qq.com ) |
| ⭐ | 金银花、子在川上(captainl1993@126.com) |
##### 感谢支持
Alan、Y-g、angelhappyboy、黑天小飞侠
---
#### 2023
##### 小星星
| 称号 | 名单 |
| ------------- | -------------------------------- |
| ⭐⭐⭐⭐⭐⭐⭐ | 赵少 |
| ⭐⭐⭐ | Lei Ante |
| ⭐⭐ | 张松林、-辉、北极光 |
| ⭐ | -晖、Bill、明亮北极星 |
##### 感谢支持
butxx、玩皮猫、v-v、b-e、M-a、j-s、漫步、森、葱花蛋炒饭、小许
---
#### 2022
##### 小星星
| 称号 | 名单 |
| ------------- | -------------------------------- |
| ⭐⭐ | 米斯特姚、WX768925736、猿、Micheal |
| ⭐ | -称 |
##### 感谢支持
咖喱、优优、半壁、J-n、华
================================================
FILE: LICENSE
================================================
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
.
================================================
FILE: README.md
================================================
ioGame
Lock-free, async, event-driven architecture — supports clustering & distribution out of the box, no middleware required Build decentralized, auto-scaling, multi-process distributed game servers with ease Tiny footprint, blazing-fast startup, low memory usage, zero config files, elegant route-level access control Simultaneous support for WebSocket, UDP, TCP and more — with built-in full-link distributed tracing One codebase, multiple protocols — seamlessly switch between Protobuf, JSON, and beyond Near-native performance — 11.52 million business operations per second in a single thread Code-as-documentation, JSR380 validation, assertion + exception patterns — minimal maintenance overhead Smart same-process affinity with IDE-friendly code navigation & jump-to-source Deploy your way — components run independently or fused together Write once, generate client SDKs — interactive code generation for any frontend Cross-process, cross-machine communication between logic servers Dynamic player-to-server binding Plays nicely with any framework Feels natural for web MVC developers No hard dependency on Spring Zero learning curve Pure JavaSE
Documentation: https://iohao.github.io/game/docs/intro
## Vision
Make 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.
## Open, Free & Developer-Friendly License
- There is no commercial edition — never has been, never will be. Every feature is open source.
- We're committed to at least **ten years** of active maintenance, starting from 2022-03-01.
- 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).
**Why AGPL 3.0?**
ioGame 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.
We 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.
Contrast 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.
Under AGPL 3.0, shared ownership means developers are genuinely motivated to invest in and polish their work.
## Startup Showcase
ioGame is impressively lean:
- **Memory**: Minimal footprint.
- **Startup**: Applications typically boot in under **1 second**.
- **Package size**: ~**15 MB** as a jar.

## What is ioGame?
Looking 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.
ioGame is a lightweight networking framework built for **online games, IoT, internal systems**, and any scenario that needs persistent connections.
**Key Features at a Glance:**
> 1. Truly lightweight — lock-free, async, event-driven from the ground up.
> 2. Small package, low memory, fast startup.
> 3. Pure JavaSE — integrates effortlessly with Spring, Vert.x, Quarkus, Solon, and others.
> 4. **Zero learning curve.** If you know basic Java or web MVC, you already know how to use ioGame. No game dev experience required.
> 5. Architecturally eliminates the **N×N scaling problem** that plagues traditional frameworks.
> 6. **No third-party dependencies** for clustering & distribution — just a JVM is all you need.
> 7. Three-part architecture: External Server + Broker (Gateway) + Game Logic Server — each can run **independently or fused together**, adapting to **any game type**.
> 8. Fully **dynamic scaling** — add or remove External Servers, Logic Servers, and Brokers on the fly.
> 9. **Multi-server, single-process** mode for development — debug distributed systems as easily as a monolith.
> 10. Logic Servers can run standalone, **enabling true modularization**.
> 11. Built-in **full-link distributed tracing**.
> 12. Rich communication primitives — logic servers can talk across machines seamlessly.
> 13. MVC-style coding with non-intrusive Java Beans — effectively **prevents class explosion**.
> 14. Built-in **per-player thread safety** — concurrency handled for you.
> 15. **One codebase** supports TCP, WebSocket, UDP simultaneously — no code changes needed. Extensible to KCP, QUIC, and beyond.
> 16. **One codebase** for switching data protocols — Protobuf, JSON, and more. Extensible.
> 17. **Hot-swap protocols** — add or remove protocols without restarting the gateway or External Server. No player disconnections.
> 18. Auto-boxing/unboxing of primitives in actions — solves the [protocol fragment](https://iohao.github.io/game/docs/manual/protocol_fragment) problem.
> 19. Pluggable, extensible **plugin system**.
> 20. Deploy as **single-process** or **multi-process across machines** — switch freely without code changes.
> 21. Logic Servers never expose ports — **immune to port-scanning attacks** by design.
> 22. Built-in **stress testing & simulation module** with real network conditions, continuous interaction, and automation support.
> 23. Sync, async, and async-callback methods for inter-service communication.
> 24. **Distributed event bus** (like MQ / Redis pub-sub — works across machines and processes).
> 25. Elegant **route-level access control**.
> 26. Intelligent **same-process affinity**.
> 27. JSR380 validation + assertions + exceptions = **less boilerplate, fewer bugs**.
> 28. **Write once, generate everywhere** — produce unified interactive SDKs for Godot, UE, Unity, Cocos Creator, Laya, React, Vue, Angular, and more. Massive productivity boost.
Packaging, memory, and startup are all best-in-class: jar size ~**15 MB**, startup typically under **1 second**, low memory footprint.
**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.
**Zero learning curve.** If you know basic Java or web MVC patterns, you're ready. No game development background needed.
**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.
**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.
**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.
**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.
**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.
**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.
**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.
**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**.
**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.
**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:
1. No redundant development.
2. Low coupling between modules.
3. True single-responsibility design — each feature becomes its own **logic server**.
4. Scale any module independently without code changes.
5. Build up your own **ecosystem arsenal** of reusable components for competitive advantage.
6. **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.
7. Admins deploy the gateway and External Server on the internal network; developers code and test their own modules locally. Additional perks:
- Client connections survive logic server restarts.
- Developers don't need to run each other's modules.
- Auto-generated docs handle inter-module integration.
**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.
**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:
- **Client-facing models:**
- [request/response](https://iohao.github.io/game/docs/communication/request_response)
- request/void (fire-and-forget)
- request/broadcast
- [broadcast](http://localhost:3000/docs/communication/broadcast) (server push)
- **Internal (server-to-server) models:**
- [request/response](https://iohao.github.io/game/docs/communication/request_response)
- request/void
- [request/multiple_response](https://iohao.github.io/game/docs/communication/request_multiple_response) — fan-out to multiple logic servers of the same type
- [EventBus](https://iohao.github.io/game/docs/communication/event_bus) — distributed event bus
- [ExternalRegion](https://iohao.github.io/game/docs/communication/external_biz_region) — access External Servers
Since ioGame 21, **virtual threads** are used for blocking inter-service communication, preventing business thread starvation and significantly boosting throughput.
**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.
**Lock-free concurrency.** ioGame's elegant thread executor design lets developers write high-concurrency code without locks — naturally and safely.
**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.
**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.
**Hot-swap protocols.** Add or remove protocols **without restarting** the External Server or Broker — no player disconnections, no fleet-wide restarts.
**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.
**[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.
**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.
**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.
**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.
**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**.
**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.
**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).
**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.
**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.
**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.
We 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!
---
## Write Once, Generate Everywhere — A Massive Productivity Boost
ioGame is built around the principle of **code-as-documentation** and **methods-as-interfaces**.
**Write once** means writing your Java business code a single time. **Generate everywhere** means automatically producing client interaction code for any frontend project.
Write 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.
ioGame 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.
**Why generated client code matters:**
1. **Eliminates boilerplate.** Client developers no longer write mountains of template code.
2. **Crystal-clear semantics.** Generated interfaces explicitly define parameter types and whether to expect a response — no guesswork.
3. **Type-safe parameters.** Precise interface definitions mean type-safe method signatures, fewer security risks, and **fewer integration bugs**.
4. **Code is the documentation.** Generated code includes docs and usage examples — **zero learning cost**, even for newcomers.
5. **Focus on business logic.** Client developers can ignore server communication plumbing and **spend their time on what matters**.
6. **Smooth integration.** Using generated code feels **as natural as calling a local method** — minimal cognitive overhead for both teams.
7. **Interface-oriented, not protocol-oriented.** A modern approach that replaces the traditional protocol-centric integration workflow.
8. **Always in sync.** When your Java code changes, documentation and interfaces update automatically — **no separate docs to maintain**.
## Architecture Overview
> Lock-free, async, event-driven architecture. Truly lightweight — build a clustered, distributed game server with zero middleware.
>
> Decentralized cluster nodes, automated clustering, built-in load balancing, distributed deployment, dynamic machine scaling.
| Component | Scaling | Responsibility |
| ------------------------------------------------------------ | ------------ | -------------------------------- |
| **ExternalServer** — [External Server](https://iohao.github.io/game/docs/overall/external_intro) | Distributed | Player connections & interaction |
| **GameLogicServer** — [Game Logic Server](https://iohao.github.io/game/docs/overall/logic_intro) | Distributed | Business logic processing |
| **BrokerCluster** — [Broker (Gateway)](https://iohao.github.io/game/docs/overall/broker_intro) | Clustered | Request scheduling & forwarding |

For details, see the [Architecture Guide](https://iohao.github.io/game/docs/overall/architecture_intro).
------
> The Broker (Gateway) runs as a **cluster** — typically stateless, focused on scheduling and forwarding.
>
> 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.
>
> **Example:** Two type-A Logic Servers (A-1, A-2) share requests via the gateway's random load-balancing strategy.
>
> 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.
>
> 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.
>
> **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.
**External Server**
The 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.
Scaling External Servers provides natural load balancing and traffic control under high concurrency. Thanks to trivial scaling, supporting **millions** of concurrent players is entirely achievable.
Even 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.
Wondering 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.
## Quick Start
Here's a simplified view of how a game engine interacts with the game server:

> 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.
>
> **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.
**Data Protocol**
Define two simple data protocols for client-server communication. These are jprotobuf objects — a simplified wrapper around Google Protobuf with equivalent performance.
Think of them as DTOs — carriers for business data:
```java
@ProtobufClass
public class LoginVerifyMessage {
public String jwt;
}
@ProtobufClass
public class UserMessage {
public String name;
}
```
**Action**
Here's your server-side business logic. This code simultaneously supports TCP, WebSocket, and UDP — no changes needed:
```java
@Slf4j
@ActionController(1)
public class DemoAction {
@ActionMethod(0)
public UserMessage here(LoginVerifyMessage message) {
var userMessage = new UserMessage();
userMessage.name = "Michael Jackson, " + message.jwt;
return userMessage;
}
}
```
Each method (like `here`) is an [Action](https://iohao.github.io/game/docs/manual/action) — a unit of business logic.
Method parameters receive data from the frontend. The return value is automatically sent back to the client. You don't need to understand framework internals.
Notice 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.
**Game programming really is this simple.**
**Q: Am I ready to start building a game server?**
> Yes. Yes you are.
**Console Output Example**
When an action is called, the console logs:
```text
┏━━━━━ Debug. [(DemoAction.java:5).here] ━━━━━ [cmd:1-0 65536] ━━━━━ [xxxLogicServer - id:[76526c134cc88232379167be83e4ddfc]
┣ userId: 1
┣ Params: message : LoginVerifyMessage(jwt=hello)
┣ Response: UserMessage(name=Michael Jackson, hello)
┣ Time: 1 ms (total business method execution time)
┗━━━━━ [ioGameVersion] ━━━━━ [Thread:User-8-2] ━━━━━━━ [traceId:956230991452569600] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**What each field means:**
- **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.
- **userId** — The requesting player's ID.
- **Params** — Input from the frontend.
- **Response** — The return value, automatically pushed to the client.
- **Time** — Execution duration — use this to spot and optimize slow logic.
- **cmd** — The [route](https://iohao.github.io/game/docs/manual/cmd) (unique address) for this action.
- **ioGameVersion** — Current framework version.
- **Thread** — The thread that executed this action.
- **traceId** — Unique per-request ID for distributed tracing.
- **Logic Server** — Which Logic Server handled the request.
This visibility eliminates the most common time sinks in game development:
- "Did the client actually send the data?" *(Parameter issues)*
- "Did the server actually respond?" *(Response issues)*
- "Why is the client not getting responses?" *(Timing issues)*
Code navigation lets developers jump to any business method instantly — invaluable in team settings for understanding and modifying execution flow.
## Who is ioGame For?
1. Web developers curious about game server development.
2. Developers new to the game industry.
3. Anyone interested in game development — no prior experience needed.
4. Learners who want to see design patterns applied in practice.
5. Developers open to modern approaches.
6. Anyone ready to leave legacy codebases behind.
At least one year of hands-on programming experience is recommended.
================================================
FILE: README_CN.md
================================================
Documentation: https://iohao.github.io/game/docs/intro
## 愿景
让网络游戏服务器的编程变得轻松简单。 改变行业现状,降低使用难度,让游戏开发领域的生产资料公有制!
## 开放、自由、友好的开源协议
- 过去、现在、将来都不会有商业版本,所有功能全部开源。
- 承诺项目的维护周期是十年起步, 2022-03-01 起,至少十年维护期。
- ioGame 是一个轻量级的网络编程框架,适用于**网络游戏服务器、物联网、内部系统**及各种需要长连接的场景。 源码完全开放、最新文档阅读完全开放、提供高质量的使用文档,使用完全自由、免费(遵守开源协议)。
**友好的开源协议**
ioGame 使用 [AGPL3.0](https://www.gnu.org/licenses/agpl-3.0.txt) 开源协议,开发者在使用时需要遵守该协议。 在该协议下开发的项目是免费的,没有任何成本。
同时,该协议对开发者更加友好,它确保了项目所有权在资本家和开发者之间共享。 这意味着,即使开发者带着项目离开公司或自行运营,也依然是合法的。
与 Apache 2.0、MIT 等其他协议不同,那些协议下,一旦开发者离开公司,便会彻底失去对项目的控制权,难以获得任何实质性回报。 尤其是在开发阶段,程序员辛勤付出、996 加班,但当项目即将上线或刚上线时,却可能面临被裁的风险。 最终,他们只能眼睁睁看着自己倾注心血的成果被资本家掌控,而自己却一无所有。
因此,采用 AGPL 3.0 协议的项目,由于开发者能共享所有权,更能激励程序员全身心投入,用心打磨项目。
## 启动展示
ioGame 在内存占用、启动速度、打包等方面也是优秀的。
- 内存方面:内存占用小。
- 启动速度方面:应用通常会在 **0.x 秒**内完成启动。
- 打包方面:打 jar 包后大约 **15MB** 。

## 介绍
你是否想要开发一个**高性能、稳定、易用、自带负载均衡、避免类爆炸设计、可跨进程跨机器通信、集群无中心节点、集群自动化、有状态多进程的分布式的**网络编程服务器呢? 如果是的话,这里向你推荐一个由 java 语言编写的网络编程框架 ioGame。
ioGame 是一个轻量级的网络编程框架,适用于**网络游戏服务器、物联网、内部系统**及各种需要长连接的场景。
**ioGame 有以下特点:**
> 1. 真轻量级、无锁异步化、事件驱动的架构设计。
> 2. 包体小、内存占用少、启动速度快。
> 3. ioGame 是纯 javaSE 的,使得 ioGame 能与其他框架方便的进行集成、融合,如 Spring ...等。
> 4. 在学习成本方面,ioGame 的学习成本非常低,可以说是**零学习成本**,即使没有游戏编程经验,也能轻松上手。开发者只需掌握普通的 java 方法或 webMVC 相关知识,就能用框架开发业务。
> 5. ioGame 在架构上解决了传统框架所产生的 **N\*N 问题**。
> 6. 在轻量级方面,ioGame **不依赖任何第三方**中间件或数据库**就能支持集群、分布式**,只需要 java 环境就可以运行。
> 7. 在架构灵活性方面,ioGame 的架构由三部分组成:1.游戏对外服、2.Broker(游戏网关)、3.游戏逻辑服。三者既可相互独立,又可相互融合,这意味着使用 ioGame 可以**适应任何类型的游戏**。
> 8. 架构是可以动态扩缩的,游戏对外服、游戏逻辑服、Broker(游戏网关)都**支持动态增加和减少**。
> 9. 在分布式开发体验方面,ioGame 支持多服单进程的启动方式,这使得开发者在开发和调试分布式系统时更加简单。
> 10. 在生态规划方面,游戏逻辑服是支持独立运行,**从而实现功能模块化的可能性**。
> 11. 具备全链路调用日志跟踪特性。
> 12. 在通讯方式方面,提供多种通讯方式,且逻辑服之间可以相互跨机器通信。
> 13. 在编码风格上,提供了类 MVC 的编码风格(无入侵的 Java Bean ),这种设计方式很好的**避免了类爆炸**。
> 14. 在线程安全方面,框架为开发者解决了单个玩家的**并发问题**。
> 15. 在连接方式方面,允许开发者**使用一套业务代码**,同时支持 TCP、WebSocket、UDP 等多种连接方式,无需进行任何修改,并且可扩展。
> 16. 在数据协议方面,ioGame 让开发者**用一套业务代码**,就能轻松切换不同的数据协议,如 Protobuf、JSON 等,并且可扩展。
> 17. 在增减协议方面,ioGame 可以让你在**新增或减少协议**时,**无需重启**游戏对外服与 Broker(游戏网关)。这样既能避免玩家断线,又能避免因新增、减少协议而重启所有机器的痛点。
> 18. action 支持自动装箱、拆箱基础类型,用于解决[协议碎片](https://iohao.github.io/game/docs/manual/protocol_fragment)的问题。
> 19. 业务框架提供了插件机制,插件是可插拨、可扩展的。
> 20. 在部署方面,ioGame 支持**多服单进程**的方式部署,也支持**多服多进程**多机器的方式部署,在部署方式上可以随意的切换而不需要更改代码。
> 21. 在安全方面,所有的游戏逻辑服不需要开放端口,**天然地避免了扫描攻击**。
> 22. 在模拟客户端测试方面,ioGame 提供了压测&模拟客户端请求模块。该模块**可以模拟真实的网络环境**,并且在模拟测试的过程中与服务器的交互是可持续的、可互动的,同时也是支持自动化的。
> 23. 框架为开发者提供了同步、异步、异步回调的方法,用于逻辑服之间的相互访问。
> 24. 分布式事件总线支持(类似 MQ、Redis 发布订阅机制,可跨多个机器通信、可跨多个进程通信)。
> 25. 提供优雅的路由访问权限控制。
> 26. 具备智能的同进程亲和性。
> 27. JSR380 验证、断言 + 异常机制 = 更少的维护成本。
> 28. 一次编写到处对接,提升巨大的生产力,能为各客户端生成可交互的代码。你只需要编写一次 java 代码,就能为 Godot、UE、Unity、CocosCreator、Laya、React、Vue、Angular ...等项目生成统一的交互接口。
ioGame 在打包、内存占用、启动速度等方面也是优秀的。 打 jar 包后大约 **15MB**,应用通常会在 **0.x 秒**内完成启动,内存占用小。
在生态融合方面,ioGame 可以很方便的[与 Spring 集成](https://iohao.github.io/game/docs/manual/integration_spring)(4 行代码)。 除了 Spring 外,还能与任何其他的框架做**融合**,如 Vert.x、Quarkus、Solon ...等,从而使用其他框架的相关生态。
在学习成本方面,ioGame 的学习成本非常低,可以说是**零学习成本**,即使没有游戏编程经验也能轻松上手。 开发者只需掌握普通的 java 方法或 webMVC 相关知识,就能使用框架开发业务。
在编码风格上,ioGame 为开发者提供了类 MVC 的编码风格(无入侵的 Java Bean ),这种设计方式很好的**避免了类爆炸**。 同时,框架为开发者提供了同步、异步、异步回调的方法,用于逻辑服之间的相互访问。 这使得开发者所编写的代码会非常的优雅,并且具备全链路调用日志跟踪。
与客户端对接方面,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++,并支持扩展。
ioGame 在架构上解决了传统框架所产生的 **N\*N 问题**([与传统架构对比](https://iohao.github.io/game/docs/overall/legacy_system))。 传统架构在扩展机器时,需要借助很多第三方中间件,如:Redis、MQ、ZooKeeper ...等,才能满足整体架构的运作。 通常,只要引入了需要安装的中间件才能做到扩展的,那么你的架构或者说框架,基本上与轻量级无缘了。
在轻量级方面,ioGame **不依赖任何第三方**中间件或数据库**就能支持集群、分布式**,只需要 java 环境就可以运行。 这意味着在使用上简单了,在部署上也为企业减少了部署成本、维护难度。使用 ioGame 时,只需一个依赖即可获得整个框架, 而无需安装其他服务,如: Nginx、Redis、MQ、Mysql、ZooKeeper、Protobuf 协议编译工具 ...等。
在[架构灵活性](https://iohao.github.io/game/docs/overall/deploy_flexible)方面,ioGame 的架构由三部分组成:1.游戏对外服、2.Broker(游戏网关)、3.游戏逻辑服。 三者既可相互独立,又可相互融合。 这意味着使用 ioGame 可以**适应任何类型的游戏**,因为只需通过调整部署方式,就可以满足不同类型的游戏需求。 在 ioGame 中进行这些调整工作非常简单,而且不会对现有代码产生不良影响。
架构是可以动态扩缩的,游戏对外服、游戏逻辑服、Broker(游戏网关)都**支持动态增加和减少**。 无论未来玩家数量增加或减少,我们都能够轻松应对。 同时,架构是**支持玩家无感知更新**的,这得益于分布式设计。 举例来说,如果 A 类型的游戏逻辑服需要增加一些新功能,我们可以启动 A-3、A-4 等已经支持了新功能的服务器, 然后逐步将之前的 A-1 和 A-2 下线,从而实现了无感知的更新。
在集群方面,ioGame 的 Broker (游戏网关)采用无中心节点、[自动化的集群设计](https://iohao.github.io/game/docs/examples/server/example_broker_cluster),所有节点平等且自治,不存在单点故障。 集群能够**自动管理和弹性扩缩**,节点加入或退出时,能够自动保证负载均衡和数据一致性,不影响服务可用性。
在分布式方面,ioGame 的逻辑服使用了分布式设计思想,将服务器分为[游戏对外服](https://iohao.github.io/game/docs/overall/external_intro)、[游戏逻辑服](https://iohao.github.io/game/docs/overall/logic_intro)等不同层次, 并且每一层都有明确的职责和接口。这样可以提高代码可读性和可维护性,并且方便进行**水平扩展**。
在分布式开发体验方面,通常在开发分布式应用时是需要启动多个进程的。 这会让调试与排查问题变得非常困难,从而降低开发者的效率、增加工作量等,这也是很多框架都**解决不了的问题**,但 ioGame 做到了! ioGame 支持多服单进程的启动方式,这使得开发者在开发和调试分布式系统时更加简单。
在[生态规划](https://iohao.github.io/game/docs/manual_high/your_ecology)方面,我们的游戏逻辑服是支持独立运行的,只需接入 Broker(游戏网关)上, 就可以为玩家和其他游戏逻辑服提供功能上的扩展与增强。 我们可以将一些**游戏逻辑服组件化**,并制作成相对通用的组件,**从而实现功能模块化的可能性**。这么做有几个优点
1. 避免一些重复开发的工作量。
2. 减少各功能模块的耦合。
3. 更符合单一职责的设计,将相对通用的功能扩展成一个个的**功能逻辑服**。如,公会逻辑服、好友逻辑服、登录逻辑服、抽奖逻辑服、公告逻辑服、排行榜逻辑服...等。
4. 由于模块功能是独立,那么将来可以对任意的功能逻辑服进行扩容,且不需要改动任何代码。
5. 这些组件化后的功能逻辑服就好比一件件武器,积累得足够多时就形成了自己的生态武器库,可以更好的帮助公司与同行竞争。
6. **代码泄漏机率更小**。传统的游戏项目通常采用单机结构,把所有的代码放在一个目录中。这样做有很大的风险,因为如果代码泄漏了,就会泄漏整个项目的内容。当功能模块化后,可以让不同的开发人员只负责自己的游戏逻辑服模块,从而避免代码泄漏的风险和影响。
7. 团队管理员只需要在内网服务器上部署一个游戏网关和游戏对外服,而开发人员就可以在本机上编码和测试自己的游戏逻辑服模块。这样还有以下好处
- 游戏客户端不会因为游戏逻辑服的变更或重启而断开连接。
- 开发人员不需要启动其他人的游戏逻辑服模块。
- 开发人员可以通过 ioGame 自动生成的文档来进行模块间的对接。
ioGame 具备[全链路调用日志跟踪](https://iohao.github.io/game/docs/manual/trace)特性,这在分布式下非常的实用。 该特性为每个请求分配一个唯一标识,并记录在日志中,通过唯一标识可以快速的在日志中过滤出指定请求的信息。 ioGame 提供的全链路调用日志跟踪特性更是强大,**支持跨机器、跨进程**。 简单的说,从玩家的请求进来到结束,无论该请求经过了多少个游戏逻辑服,都能精准记录。
在通讯方式方面,大部分框架只能支持推送(广播)这一类型的通讯方式。 ioGame 则提供了多种[通讯模型](https://iohao.github.io/game/docs/manual/communication_model), 通过对各种通讯方式的组合使用,可以简单完成以往难以完成的工作, 并且这些通讯方式都支持跨进程、跨机器通信,且具备全链路调用日志跟踪。
- 在客户端的角度,提供了如下的通讯模型
- [request/response](https://iohao.github.io/game/docs/communication/request_response),请求/响应
- request/void,请求/无响应
- request/broadcast,请求/广播响应
- [broadcast](http://localhost:3000/docs/communication/broadcast),广播
- 内部通讯主要用于服务器内部之间的通信,跨服、跨进程通信。提供了如下的通讯模型
- [request/response](https://iohao.github.io/game/docs/communication/request_response),请求/响应
- request/void,请求/无响应
- [request/multiple_response](https://iohao.github.io/game/docs/communication/request_multiple_response),同时请求同类型多个游戏逻辑服
- [EventBus](https://iohao.github.io/game/docs/communication/event_bus),分布式事件总线
- [ExternalRegion](https://iohao.github.io/game/docs/communication/external_biz_region),访问游戏对外服
从 ioGame21 开始,框架添加了虚拟线程的相关支持。 各逻辑服之间通信阻塞部分使用虚拟线程,这样可以很好的避免阻塞业务线程,并大幅提高了框架的吞吐量。
在线程安全方面,框架为开发者解决了单个玩家的**并发问题**。 即使玩家重新登录后,也会使用相同的线程来消费业务,并推荐使用[领域事件](https://iohao.github.io/game/docs/extension_module/domain_event)来解决同一房间或业务内多个玩家的并发问题。 [框架在线程的扩展性](https://iohao.github.io/game/docs/overall/thread_executor)上提供了友好的支持,开发者可以很容易的编写出无锁并发代码,这得益于 ioGame 独有的线程执行器设计与扩展。 换句话说,你不会因为并发问题烦恼。
在无锁并发方面,ioGame 提供了优雅、独特的线程执行器设计。通过该特性,开发者能轻易的编写出无锁高并发的代码。
在连接方式方面,ioGame 允许开发者**使用一套业务代码**,**同时支持**多种连接方式,无需进行任何修改。 ioGame 已经支持了 TCP、WebSocket 和 UDP 连接方式,并且也支持在这几种连接方式之间进行灵活切换。 连接方式是可扩展的,并且扩展操作也很简单,这意味着之后如果支持了 KCP、QUIC, 无论你当前项目使用的是 TCP、WebSocket、UDP,都可以切换成 KCP、QUIC。 即使切换到 KCP、QUIC 的连接方式,现有的业务代码也无需改变。
在通信协议方面,ioGame 让开发者**用一套业务代码**,就能轻松[切换和扩展不同的数据协议](https://iohao.github.io/game/docs/manual/data_protocol), 如 Protobuf、JSON 等。只需一行代码,就可以从 Protobuf 切换到 JSON,无需改变业务方法。
在增减协议方面,ioGame 可以让你在**新增或减少协议**时,**无需重启**游戏对外服与 Broker(游戏网关)。 这样既能避免玩家断线,又能避免因新增、减少协议而重启所有机器的痛点。
在协议碎片方面,action 支持自动装箱、拆箱基础类型特性,用于解决[协议碎片](https://iohao.github.io/game/docs/manual/protocol_fragment)的问题。 同时该特性除了能使你的业务代码更加清晰以外,还能大幅提高开发者在该环节的生产力。
在[同进程亲和性](https://iohao.github.io/game/docs/manual_high/same_process)方面,在同一进程内, 不同 Netty 实例之间的通信,是通过内存进行传输的,不需要经过网络传输,数据传输速度极快。 同进程亲和性指的是,优先访问同进程内的游戏逻辑服,当同进程内没有能处理请求的游戏逻辑服时, 才会去其他进程或机器中查找能处理请求的游戏逻辑服。 简单点说,框架对于请求的处理很智能,会优先将请求给同进程内的逻辑服消费。
在开发体验方面,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 支持自动装箱、拆箱基础类型,用于解决协议碎片的问题 ...等。 诸多丰富的功能,使得开发者的业务代码更加的清晰、简洁。
业务框架提供了[插件](https://iohao.github.io/game/docs/manual/plugin_intro)机制,插件是可插拨、可扩展的。 框架内置提供了 DebugInOut、action 调用统计、业务线程监控插件、各时间段调用统计插件...等插件。 不同的插件提供了不同的关注点,比如我们可以使用调用、监控等插件相互配合,可以让我们在开发阶段就知道**是否存在性能问题**。 合理利用好各个插件,可以让我们在开发阶段就能知道问题所在,提前发现问题,提前预防问题。
在部署方面,ioGame 支持**多服单进程**的方式部署,也支持**多服多进程**多机器的方式部署,在部署方式上可以随意的切换而不需要更改代码。 日常中我们可以按照单体思维开发,到了生产可以选择性的使用多进程的方式部署。
在安全方面,所有的游戏逻辑服[不需要开放端口,天然地避免了扫描攻击](https://iohao.github.io/game/docs/overall/legacy_system#Usage-Management)。 由于不需要为每个逻辑服分配独立的端口,那么我们在使用诸如云服务器之类的服务时,就不需要担心端口开放权限的问题了。 别小看这一个环节,通常这些小细节最浪费开发者的时间。 由于我们不需要管理这些 IP:Port,**这部分的工作量就自然地消失了**。
在模拟客户端测试方面,ioGame 提供了[压测&模拟客户端请求](https://iohao.github.io/game/docs/extension_module/simulation_client)模块。 此模块是用于模拟客户端,简化模拟工作量,只需要编写对应请求与回调。 除了可以模拟简单的请求外,通常还可以做一些复杂的请求编排,并支持复杂业务的压测。 **与单元测试不同的是,该模块可以模拟真实的网络环境,并且在模拟测试的过程中与服务器的交互是可持续的、可互动的,同时也是支持自动化的**。
使用 ioGame,可以显著的帮助企业减少巨额成本。 文档中,**成本**关键字提到了很多次,各个阶段均有关联,包括了学习、研发、测试、部署、扩展、投入 ...等各阶段。 在同等资源的竞争下,使用 ioGame 能为公司节省更多的资源,从而提高了自身的生存率。 更重要的是避免了为其他公司做嫁衣的可能性,具体可阅读[成本分析案例](https://iohao.github.io/game/services/cost_analysis)。
开发者基于 ioGame 编写的项目模块,通常是条理清晰的,得益于框架对**路由的合理设计**,同时也为路由提供了优雅的[访问权限控制](https://iohao.github.io/game/docs/external/access_authentication)。 当我们整理好这些模块后,对于其他开发者接管项目或后续的维护中,会是一个不错的帮助([代码组织与约定](https://iohao.github.io/game/docs/manual_high/code_organization))。 或许现阶段你感受不到这块的威力,随着你深入地使用实践就能体会到这么设计的诸多好处与优势。
开发者基于 ioGame 编写的项目,通常是语法简洁的、高性能的、低延迟的。 框架最低要求使用 **JDK21**,这样即可以让项目享受到**分代 ZGC** 带来的改进,还能享受语法上的简洁。 分代 ZGC 远低于其**亚毫秒级**暂停时间的目标,可以在不影响游戏速度的情况下,清理掉多余的内存。 这样就不会出现卡顿或者崩溃的问题了,相当于在项目中变相的引入了一位 JVM 调优大师。
综上所述,ioGame 是一个非常适合网络游戏开发的框架。可以让你轻松地创建高性能、低延迟、易扩展的游戏服务器,并且节省时间和资源。 如果你想要快速地开发出令人惊艳的网络游戏,请不要犹豫,立即选择 ioGame 吧! 框架屏蔽了很多复杂且重复性的工作,并可为项目中的功能模块结构、开发流程等进行**清晰的组织定义**,减少了后续的项目维护成本。
框架在开发、部署、压测&模拟测试 ...等,各个阶段都提供了很好的支持。 相信你已经对 ioGame 有了一个初步的了解,虽然还有很多丰富的功能与特性没有介绍到,但你可以通过后续的实践过程中来深入了解。 感谢你的阅读,并期待你使用 ioGame 来打造自己的游戏服务器。
---
## 一次编写到处对接,提升巨大的生产力
ioGame 是非常注重开发体验的,代码注释即文档、方法即交互接口的原则。
ioGame 具备一次编写到处对接的能力,从而让你们团队提升巨大的生产力。 **一次编写**指的是编写一次 java 业务代码,而**到处对接**则是指为不同的前端项目生成与服务器交互的代码。
你只需要编写一次 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/) ...等项目生成统一的交互接口
ioGame 能为各种前端项目生成 **action、广播、错误码** 相关接口代码。 这将意味着,你只需要编写一次业务代码,就可以同时与这些游戏引擎或现代化的前端框架交互。
前端代码生成的几个优势
1. 帮助客户端开发者减少巨大的工作量,**不需要编写大量的模板代码**。
2. **语义明确,清晰**。生成的交互代码即能明确所需要的参数类型,又能明确服务器是否会有返回值。这些会在生成接口时就提前明确好。
3. 由于我们可以做到明确交互接口,进而可以明确参数类型。这使得**接口方法参数类型安全、明确**,从而有效避免安全隐患,从而**减少联调时的低级错误**。
4. 减少服务器与客户端双方对接时的沟通成本,代码即文档。生成的联调代码中有文档与使用示例,方法上的示例会教你如何使用,即使是新手也能做到**零学习成本**。
5. 帮助客户端开发者屏蔽与服务器交互部分,**将更多的精力放在真正的业务上**。
6. 为双方联调减少心智负担。联调代码使用简单,**与本地方法调用一般丝滑**。
7. 抛弃传统面向协议对接的方式,转而**使用面向接口方法的对接方式**。
8. 当我们的 java 代码编写完成后,我们的文档及交互接口可做到同步更新,**不需要额外花时间去维护对接文档及其内容**。
## 架构简图
> 无锁异步化、事件驱动的架构设计;真轻量级,无需依赖任何第三方中间件或数据库就能搭建出一个集群、分布式的网络游戏服务器。
>
> 集群无中心节点、集群自动化、自带负载均衡、分布式支持、可动态增减机器。
| 名称 | 扩展方式 | 职责 |
| ------------------------------------------------------------ | -------- | ---------------- |
| **ExternalServer**,[游戏对外服](https://iohao.github.io/game/docs/overall/external_intro) | 分布式 | 与玩家连接、交互 |
| **GameLogicServer**,[游戏逻辑服](https://iohao.github.io/game/docs/overall/logic_intro) | 分布式 | 处理具体业务逻辑 |
| **BrokerCluster**,[Broker(游戏网关)](https://iohao.github.io/game/docs/overall/broker_intro) | 集群 | 调度和转发任务 |

更详细的介绍请阅读[架构介绍](https://iohao.github.io/game/docs/overall/architecture_intro)。
------
> 从图中可以看出,游戏网关支持以集群方式启动多个实例。这个设计选择了集群的方式,因为游戏网关通常是无状态的,主要用于调度和转发任务
>
> 而游戏对外服、游戏逻辑服使用分布式设计,支持启动多个相同类型的服务。这意味着,当玩家数量增加时,我们可以轻松增加相应类型的游戏逻辑服以处理更多请求。
>
> 以游戏逻辑服为例,假设我们启动了两个 A 类型的游戏逻辑服,分别为 A-1 和 A-2。当玩家向 A 类型的游戏逻辑服发起多次请求时,游戏网关会使用默认的随机负载策略将请求分配给 A-1 和 A-2 来处理。
>
> 现在我们明白,游戏对外服和游戏逻辑服都支持动态增加和减少。无论未来玩家数量增加或减少,我们都能够轻松应对。架构是**支持玩家无感知更新**的,这得益于分布式设计。举例来说,如果 A 类型的游戏逻辑服需要增加一些新功能,我们可以启动 A-3、A-4 等已经支持了新功能的服务器,然后逐步将之前的 A-1 和 A-2 下线,从而实现了无感知的更新。
>
> 此外,框架还支持玩家[动态绑定游戏逻辑服](https://iohao.github.io/game/docs/manual/binding_logic_server)。玩家与游戏逻辑服绑定后,之后的请求都由该游戏逻辑服来处理。
>
> 除了游戏之外,ioGame 也适用于物联网相关项目。只需将图中的玩家视为具体的设备,即使存在数亿个设备,ioGame 的架构也可以轻松支持。从 2022 年开始,已经有一些物联网公司开始采用这一解决方案,并得到了很好的体验。
**游戏对外服**
游戏对外服主要负责与用户(玩家)的长连接,先来个假设,假如我们的一台硬件支持我们建立用户连接的上限是 5000 人, 当用户量达到 7000 人时,我们可以多加一个对外服务器来进行分流减压。
通过增加游戏对外服的数量,可以有效地进行连接的负载均衡和流量控制,使得系统能够更好地承受高并发的压力。 由于游戏对外服扩展的简单性,意味着支持同时在线玩家可以轻松的达到百万、千万甚至更多。
即使我们启动了多个游戏对外服,开发者也不需要关心这些玩家连接到了哪个游戏对外服的问题, 这些玩家总是能接收到广播(推送)消息的,因为框架已经把这些事情给做了。 在玩家的角度我们只有“一个”服务器,同样的,在开发者的角度我们只有“一个”游戏对外服。
通常,有些开发者想知道游戏对外服最大支持多少玩家连接。 关于这个问题,只需要搜索 Netty 相关知识即可,因为游戏对外服本质上是 Netty。
同样的,如果开发者已经熟悉了 Netty 相关知识,那么在游戏对外服的扩展上也会变得非常的容易。
## 快速入门
下面是游戏引擎与游戏服务器的业务交互简图。

> 抽象的说,游戏前端与游戏服务器的的交互由上图组成。 游戏前端与游戏服务器可以自由地**双向交互**,即发送和接收业务数据。 业务数据由 .proto 文件作为载体,在前端和后端之间进行编码和解码。 .proto 文件是对业务数据的描述载体,定义了数据类型和消息类型,以及它们的属性和规则。
>
> 通过这种方式,游戏前端和游戏服务端可以建立连接,并开始相互传递业务数据,处理各自的业务。 以上是对游戏前端与游戏服务器之间交互方式的介绍。 接下来,我们将编写一个简单的游戏业务处理示例,并定制一个适合我们需求的业务数据协议。
>
> **协议文件**是对业务数据的描述载体,用于游戏前端与游戏服务器的数据交互。 Protocol Buffers 是 Google 开发的一种数据描述语言,也简称 PB。 协议文件描述还可以是 json、xml 或者任意自定义的,因为最后传输时会转换为二进制,但游戏开发中 PB 是目前的最佳选择。
>
> **游戏前端**的展现可以是 [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 ...等)。
**数据协议**
现在,我们定义两个数据协议,用于客户端与服务器的数据交互。 这是一个 jprotobuf 的 pb 对象,jprotobuf 是对 google protobuf 的简化使用,性能同等。
可以把这理解成 DTO 业务数据载体等,其主要目的是用于业务数据的传输。
```java
@ProtobufClass
public class LoginVerifyMessage {
public String jwt;
}
@ProtobufClass
public class UserMessage {
public String name;
}
```
**Action**
游戏服务器的编程,游戏服务器接收业务数据后,对业务数据进行处理。 下面这段代码可以同时支持 TCP、WebSocket、UDP 通信方式。
```java
@Slf4j
@ActionController(1)
public class DemoAction {
@ActionMethod(0)
public UserMessage here(LoginVerifyMessage message) {
var userMessage = new UserMessage();
userMessage.name = "Michael Jackson, " + message.jwt;
return userMessage;
}
}
```
一个方法(here)在业务框架中表示一个 [Action](https://iohao.github.io/game/docs/manual/action)(业务动作)。
方法声明的参数是用于接收前端传入的业务数据,在方法 return 时,数据就可以被游戏前端接收到。 程序员可以不需要关心业务框架的内部细节。
从上面的示例可以看出,这和普通的 java 类并无区别,同时这种设计方式**避免了类爆炸**。 如果只负责编写游戏业务,那么对于业务框架的学习可以到此为止了。
**游戏编程就是如此简单!**
**问:我可以开始游戏服务器的编程了吗?**
> 是的,你已经可以开始游戏服务器的编程了。
**访问示例(控制台)**
当访问 action 业务方法时,控制台将会打印的日志输出如下
```text
┏━━━━━ Debug. [(DemoAction.java:5).here] ━━━━━ [cmd:1-0 65536] ━━━━━ [xxx逻辑服 - id:[76526c134cc88232379167be83e4ddfc]
┣ userId: 1
┣ 参数: message : LoginVerifyMessage(jwt=hello)
┣ 响应: UserMessage(name=Michael Jackson, hello)
┣ 时间: 1 ms (业务方法总耗时)
┗━━━━━ [ioGameVersion] ━━━━━ [线程:User-8-2] ━━━━━━━ [traceId:956230991452569600] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**控制台打印说明**
- **Debug**. [(DemoAction.java:5).here] : 表示执行业务的是 DemoAction 类下的 here 方法,5 表示业务方法所在的代码行数。在工具中点击控制台的 DemoAction.java:5 这条信息,就可以跳转到对应的代码中(快速导航到对应的代码),这是一个开发良好体验的开始!
- **userId** : 当前发起请求的 用户 id。
- **参数** : 通常是游戏前端传入的值。
- **响应** : 通常是业务方法返回的值 ,业务框架会把这个返回值推送到游戏前端。
- **时间** : 执行业务方法总耗时,我们可根据业务方法总耗时的时长来优化业务。
- **路由信息** : [cmd - subCmd][路由](https://iohao.github.io/game/docs/manual/cmd)是唯一的访问地址。
- **ioGameVersion** : 表示当前所使用的 ioGame 版本。
- **线程** : 当前执行 action 所使用的线程。
- **traceId** : 全链路调用日志跟踪 id,每个请求唯一。(该特性在分布式下非常实用)
- **逻辑服** : 当前游戏逻辑服的 id
有了以上信息,游戏开发者可以很快的定位问题。 如果没有可视化的信息,开发中会浪费很多时间在前后端的沟通上。问题包括:
- 是否传参问题 (游戏前端说传了)
- 是否响应问题(游戏后端说返回了)
- 业务执行时长问题 (游戏前端说没收到响应, 游戏后端说早就响应了)
其中代码导航可以让开发者快速的跳转到业务类对应代码中, 在多人合作的项目中可以快速的知道业务经过了哪些方法的执行,使得我们可以快速的进行阅读或修改。
## 适合人群
1. 长期从事 web 内部系统开发人员, 想了解游戏的。
2. 刚从事游戏开发的。
3. 未从事过游戏开发,但却对其感兴趣的。
4. 对设计模式在实践中的应用有兴趣的学习者。
5. 可以接受新鲜事物的。
6. 想放弃祖传代码的。
推荐实际编程经验一年以上的人员。
================================================
FILE: changeLog_ioGame.md
================================================
文档与日志
- [框架版本更新日志](https://iohao.github.io/game/docs/version_log)
- [ioGame 真.轻量级网络编程框架 - 在线使用文档 ](https://game.iohao.com/)
> ioGame 每月会发 1 ~ 2 个版本,通常在大版本内升级总是兼容的,如 21.1 升级到任意 21.x 的高版本。
### 2025-08-11 - v21.30
https://github.com/iohao/ioGame/releases/tag/21.30
**Version update summary**
> 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# 代码生成时的转义字符
### 2025-07-19 - v21.29
https://github.com/iohao/ioGame/releases/tag/21.29
**Version update summary**
> 1. [#473](https://github.com/iohao/ioGame/issues/473) fix(core): Change the method name from DataSelfEncode getEncodeData to encodeData to avoid JSON serialization.
> 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.
> 3. feat(generate-code): [#452](https://github.com/iohao/ioGame/issues/452)
> 4. fix(generate-code): broadcast_action_example_action.txt、broadcast_action_example.txt
> 5. feat(external): [#469](https://github.com/iohao/ioGame/issues/469) Add HttpFallbackHandler to determine if it's a WebSocket upgrade request.
> 6. perf(doc): Pre-create the Pb for the BroadcastDocument dataClass.
**[other updates]**
Upgrade reactor-netty 1.2.7、commons-io 2.19.0
### 2025-06-17 - v21.28
https://github.com/iohao/ioGame/releases/tag/21.28
**Version update summary**
> 1. feat(room): The room supports convenient operations.
> 2. Deprecated RoomStatusEnum.
> 3. feat(room): The room supports convenient broadcastRange.
> 4. docs(all): Update documentation link (https://iohao.github.io/game).
> 5. [#451](https://github.com/iohao/ioGame/issues/451) Refactor the usage documentation to use the new access address: https://iohao.github.io/game/ .
------
**[other updates]**
```xml
4.1.122.Final
```
------
### 2025-05-09 - v21.27
https://github.com/iohao/ioGame/releases/tag/21.27
**Version update summary**
> 1. feat(generate-code): #449 Supports GDScript GenerateCode
> 2. #444 Provides GDScript SDK
> 3. #448 Provides GDScript Example with ioGame
> 4. perf(core): ActionCommandDocKit
------
**feat(generate-code)**: #449 Supports GDScript GenerateCode
About examples: https://github.com/iohao/ioGameSdkGDScriptExampleGodot
```java
public final class GenerateTest {
// setting root path
static String rootPath = "/Users/join/gitme/ioGame-sdk/";
public static void main(String[] args) {
// CHINA or US
Locale.setDefault(Locale.CHINA);
// Load the business framework of each gameLogicServer
// cn: 加载游戏逻辑服的业务框架
yourListLogic().forEach(BrokerClientStartup::createBarSkeleton);
/*
* Generate actions, broadcasts, and error codes.
* cn: 生成 action、广播、错误码
*/
// ----- About generating GDScript code -----
generateCodeGDScriptGodot();
// Added an enumeration error code class to generate error code related information
IoGameDocumentHelper.addErrorCodeClass(YourGameCodeEnum.class);
// Generate document
IoGameDocumentHelper.generateDocument();
}
private static void generateCodeGDScriptGodot() {
var documentGenerate = new GDScriptDocumentGenerate();
// By default, it will be generated in the target/code directory
// cn: 设置代码生成所存放的路径,如果不做任何设置,将会生成在 target/code 目录中
String path = rootPath + "ioGameSdkGDScriptExampleGodot/gen/code";
documentGenerate.setPath(path);
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
}
```
### 2025-04-30 - v21.26
https://github.com/iohao/ioGame/releases/tag/21.26
**Version update summary**
> 1. refactor(Code generation): Code generation supports importing multiple .proto files
> 2. refactor(i18n): #376
------
Supports importing multiple .proto files
```java
public interface SdkProtoFile {
String fileName = "common.proto";
String filePackage = "common";
String fileName2 = "common2.proto";
String filePackage2 = "common2";
}
@ToString
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
@ProtoFileMerge(fileName = SdkProtoFile.fileName, filePackage = SdkProtoFile.filePackage)
public final class LoginVerifyMessage {
/** jwt */
String jwt;
}
@ToString
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
@ProtoFileMerge(fileName = SdkProtoFile.fileName2, filePackage = SdkProtoFile.filePackage2)
public final class BulletMessage {
/** id */
int bulletId;
/** bullet name */
String name;
}
```
**[other updates]**
```xml
4.1.121.Final
```
### 2025-03-20 - v21.25
https://github.com/iohao/ioGame/releases/tag/21.25
**Version update summary**
> 1. fix(broker): DefaultWithElementSelector
> 2. refactor(net): enhance ResponseCollectItemMessage
> 3. refactor(core): FlowContext add the createResponseMessage method
------
**[other updates]**
```xml
4.1.119.Final
```
### 2025-02-12 - v21.24
https://github.com/iohao/ioGame/releases/tag/21.24
**Version update summary**
> 1. refactor(client): Client support boxing and unboxing
> 2. refactor(core): FlowContextKit add ofFlowContext method
> 3. refactor(room): room add getPlayerBySeat method
> 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.
> 5. refactor(core): MethodParser add parseData method
------
**[client]** Client support boxing and unboxing.
```java
// my client,support:int、long、boolean、String、List
public final class MyInputCommandRegion extends AbstractInputCommandRegion {
@Override
public void initInputCommand() {
this.inputCommandCreate.cmd = 1;
// Client support boxing and unboxing
ofCommand(2).setTitle("enterRoom").setRequestData(() -> {
// enterRoom
long roomId = 2;
return roomId;
});
// or
ofCommand(2).setTitle("enterRoom").setRequestData(() -> {
// enterRoom
return LongValue.of(2);
});
}
}
// my action
@ActionController(1)
public final class MyAction {
/**
* enterRoom
*
* @param roomId roomId
*/
@ActionMethod(2)
public void enterRoom(long roomId) {
}
}
```
------
**[other updates]**
```xml
4.1.117.Final
```
------
### 2025-01-08 - v21.23
https://github.com/iohao/ioGame/releases/tag/21.23
**Version update summary**
> 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.
> 2. perf(core): Optimize BoolValue and reduce the creation of objects
> 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.
> 4. fix(generate-code): action_method_void.txt
> 5. perf(doc): GameCode Support single parameter construction method
> 6. perf(kit): [#412](https://github.com/iohao/ioGame/issues/412)
> 7. perf(room): Player adds isRobot method. Room adds methods to distinguish between real players and robot players.
> 8. refactor(proto): [#414](https://github.com/iohao/ioGame/issues/414) Enumeration supports custom value
> 9. perf(room): room add hasSeat、isRealPlayer method
> 10. refactor(kit): RandomKit add randomLong method
> 11. refactor(core): Because the FlowContext method name setUserId is ambiguous, the method name is deprecated.
>
> 1. Deprecated FlowContext setUserId method; see bindingUserId.
> 2. Deprecated FlowContext setUserIdAndGetResult method; see bindingUserIdAndGetResult.
> 12. refactor(core): broadcastMe Added Tip: Please bind UserId before using this method, see FlowContext.bindingUserId.
> 13. perf(proto): Generate .proto files in parallel
> 14. refactor(proto): ProtoGenerateFile supports adding multiple proto packages
> 15. refactor(kit): TaskKit supports setting Timer
> 16. refactor(room): Deprecated OperationHandler verify, see processVerify
> 17. refactor(room): Add OperationCode and enhance Operation
------
About **[core]**
The name of the FlowContext setUserId method is ambiguous. This method is deprecated and replaced by the bindingUserId method.
```java
flowContext.setUserId(userId); // Deprecated
flowContext.bindingUserId(userId); // now
```
About **[kit]**
TaskKit supports setting Timer
```java
TaskKit.setTimer(new HashedWheelTimer(17, TimeUnit.MILLISECONDS));
```
About **[room]**
1. Add hasSeat method to room to check whether there are any empty seats in the room.
2. Room add isRealPlayer method.
3. Add isRobot method to Player, and add method to distinguish real players from robot players to Room.
4. Add OperationCode and enhance Operation
5. Add processVerify method to OperationHandler to control whether to execute process method, and deprecate verify method, replaced by processVerify.
When 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.
```java
public final class EnterRoomOperationHandler implements OperationHandler {
@Override
public boolean processVerify(PlayerOperationContext context) {
// assert room spaceSize
Room room = context.getRoom();
GameCode.roomSpaceSizeNotEnough.assertTrue(room.hasSeat());
long userId = context.getUserId();
long score = AccountKit.getScore(userId);
return score > 500;
}
@Override
public void process(PlayerOperationContext context) {
Room room = context.getRoom();
... enterRoom
}
}
```
Example of combining OperationCode with enumeration
```java
@ProtobufClass
@ProtoFileMerge(fileName = FileMerge.fileName, filePackage = FileMerge.filePackage)
public enum MyOperation implements OperationCode {
/** quitRoom */
quitRoom,
/** inRoom */
inRoom
;
final int operationCode;
FairOperation() {
this.operationCode = OperationCode.getAndIncrementCode();
}
@Override
public int getOperationCode() {
return operationCode;
}
}
// config
public void configOperation() {
RoomService roomService = ...
OperationFactory factory = roomService.getOperationFactory();
// mappingUser operation
factory.mappingUser(MyOperation.inRoom, new InRoomOperationHandler());
factory.mappingUser(MyOperation.quitRoom, new QuitRoomOperationHandler());
}
```
About **[proto]**
1. Optimize .proto generation and process file generation in parallel.
2. ProtoGenerateFile supports adding multiple proto packages
Supports adding multiple proto packages to better support modularization
```java
private static void generateProtoFile() {
String generateFolder = "/Users/join/gitme/game/MyGames/proto";
List protoPackageList = List.of("com.iohao.happy.robot"
,"com.iohao.happy.email");
var protoGenerateFile = new ProtoGenerateFile()
// Generate the directory where the .proto file is stored
.setGenerateFolder(generateFolder)
// The package name to be scanned
.addProtoPackage(protoPackageList)
.addProtoPackage("com.iohao.happy.common.provide.proto");
// generate .proto
protoGenerateFile.generate();
}
```
3. Enumeration supports custom values, java code and generated .proto
```java
@ProtobufClass
@ProtoFileMerge(fileName = TempProtoFile.fileName, filePackage = TempProtoFile.filePackage)
public enum AnimalTypeEnum implements EnumReadable {
/** the cat */
cat(0),
/** the tiger */
tiger(10),
;
final int value;
AnimalTypeEnum(int value) {
this.value = value;
}
@Override
public int value() {
return this.value;
}
}
```
.proto
```protobuf
// TestAnimalTypeEnum
enum AnimalTypeEnum {
// the cat
cat = 0;
// the tiger
tiger = 10;
}
```
------
**[other updates]**
```xml
4.1.116.Final
```
------
### 2024-12-02 - v21.22
https://github.com/iohao/ioGame/releases/tag/21.22
**Version update summary**
> 1. perf(core): DefaultActionMethodParamParser
> 1. fix(kit): #407 ClassRefInfoKit invokeSetter
> 1. #376 i18n DefaultUserHook
> 1. feat(GenerateCode): #329 Added TypeScript code generation TypeScriptDocumentGenerate, which can generate interactive code for CocosCreator、Vue、Angular.
------
**feat(GenerateCode)**: #329 Added TypeScript code generation TypeScriptDocumentGenerate, which can generate interactive code for CocosCreator、Vue、Angular.
About examples
1. ioGameServerExample: https://github.com/iohao/ioGameExamples/tree/main/SdkExample
2. CocosCreatorExample: https://github.com/iohao/ioGameSdkTsExampleCocos
3. VueExample: https://github.com/iohao/ioGameSdkTsExampleVue
4. HtmlExample: https://github.com/iohao/ioGameSdkTsExampleHtml
5. AngularExample: https://github.com/iohao/ioGameSdkTsExampleAngular
```java
public final class GenerateTest {
// setting root path
static String rootPath = "/Users/join/gitme/ioGame-sdk/";
public static void main(String[] args) {
// CHINA or US
Locale.setDefault(Locale.CHINA);
// Load the business framework of each gameLogicServer
// 加载游戏逻辑服的业务框架
yourListLogic().forEach(BrokerClientStartup::createBarSkeleton);
/*
* Generate actions, broadcasts, and error codes.
* cn: 生成 action、广播、错误码
*/
// About generating TypeScript code
// generateCodeVue();
// generateCodeAngular();
// generateCodeHtml();
generateCocosCreator();
// Added an enumeration error code class to generate error code related information
IoGameDocumentHelper.addErrorCodeClass(YourGameCodeEnum.class);
// Generate document
IoGameDocumentHelper.generateDocument();
}
private static void generateCodeVue() {
var documentGenerate = new TypeScriptDocumentGenerate();
// 设置代码生成所存放的路径,如果不做任何设置,将会生成在 target/code 目录中
// By default, it will be generated in the target/code directory
String path = rootPath + "ioGameSdkTsExampleVue/src/assets/gen/code";
documentGenerate.setPath(path);
// Your .proto path: Set the import path of common_pb in Vue.
documentGenerate.setProtoImportPath("../common_pb");
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
private static void generateCodeHtml() {
var documentGenerate = new TypeScriptDocumentGenerate();
// 设置代码生成所存放的路径,如果不做任何设置,将会生成在 target/code 目录中
// By default, it will be generated in the target/code directory
String path = rootPath + "ioGameSdkTsExampleHtml/src/assets/gen/code";
documentGenerate.setPath(path);
// Your .proto path: Set the import path of common_pb in Vue.
documentGenerate.setProtoImportPath("../common_pb");
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
private static void generateCocosCreator() {
var documentGenerate = new TypeScriptDocumentGenerate();
// 设置代码生成所存放的路径,如果不做任何设置,将会生成在 target/code 目录中
// By default, it will be generated in the target/code directory
String path = rootPath + "ioGameSdkTsExampleCocos/assets/scripts/gen/code";
documentGenerate.setPath(path);
// Your .proto path: Set the import path of common_pb in CocosCreator
documentGenerate.setProtoImportPath("db://assets/scripts/gen/common_pb");
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
private static void generateCodeAngular() {
var documentGenerate = new TypeScriptDocumentGenerate();
// 设置代码生成所存放的路径,如果不做任何设置,将会生成在 target/code 目录中
// By default, it will be generated in the target/code directory
String path = rootPath + "ioGameSdkTsExampleAngular/src/assets/gen/code";
documentGenerate.setPath(path);
// Your .proto path: Set the import path of common_pb in Vue.
documentGenerate.setProtoImportPath("../common_pb");
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
}
```
Advantages of SDK Code Generation
1. Helps client-side developers reduce significant workload by eliminating the need to write a large amount of template code.
2. Clear and semantically precise. The generated interaction code clearly defines parameter types and return types.
3. Ensures parameter type safety and clarity in interface methods, effectively avoiding security risks and reducing basic errors during integration.
4. 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.
5. Helps client-side developers abstract away the interaction with the server, allowing them to focus more on the core business logic.
6. Reduces the cognitive load during integration. The code is simple to use, similar to local method calls.
7. Abandons the traditional protocol-based approach in favor of an interface-method-based integration approach.
------
### 2024-11-15 - v21.20
https://github.com/iohao/ioGame/releases/tag/21.20
**Version update summary**
> 1. feat(GenerateDoc): Add DocumentMethod annotation : Action supports generating documentation method names through annotations.
> 1. BroadcastDebug enhancements.
> 1. feat(GenerateCode): #328 Added C# code generation CsharpDocumentGenerate, which can generate interactive code for Unity and Godot.
------
**feat(GenerateDoc):** Add DocumentMethod annotation : Action supports generating documentation method names through annotations.
By 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.
```java
@ActionController(SdkCmd.cmd)
public final class SdkAction {
@ActionMethod(SdkCmd.noReturn)
@DocumentMethod("noReturnMethod")
public void noReturn(String name) {
... ...
}
}
```
---
**feat(GenerateCode):** #328 Added C# code generation CsharpDocumentGenerate, which can generate interactive code for Unity and Godot.
About examples
1. see https://github.com/iohao/ioGameExamples/tree/main/SdkExample
2. UnityExample: https://github.com/iohao/ioGameSdkCsharpExampleUnity
3. GodotExample: https://github.com/iohao/ioGameSdkCsharpExampleGodot
```java
public final class GenerateTest {
// setting root path
static String rootPath = "/Users/join/gitme/ioGame-sdk/";
public static void main(String[] args) {
// CHINA or US
Locale.setDefault(Locale.CHINA);
// Load the business framework of each gameLogicServer
// 加载游戏逻辑服的业务框架
yourListLogic().forEach(BrokerClientStartup::createBarSkeleton);
/*
* Generate actions, broadcasts, and error codes.
* cn: 生成 action、广播、错误码
*/
// About generating C# code
generateCodeCsharpGodot();
generateCodeCsharpUnity();
// Added an enumeration error code class to generate error code related information
IoGameDocumentHelper.addErrorCodeClass(YourGameCodeEnum.class);
// Generate document
IoGameDocumentHelper.generateDocument();
}
private static void generateCodeCsharpUnity() {
var documentGenerate = new CsharpDocumentGenerate();
// 设置代码生成所存放的路径,如果不做任何设置,将会生成在 target/code 目录中
// By default, it will be generated in the target/code directory
String path = rootPath + "ioGameSdkCsharpExampleUnity/Assets/Scripts/Gen/Code";
documentGenerate.setPath(path);
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
private static void generateCodeCsharpGodot() {
var documentGenerate = new CsharpDocumentGenerate();
// 设置代码生成所存放的路径,如果不做任何设置,将会生成在 target/code 目录中
// By default, it will be generated in the target/code directory
String path = rootPath + "ioGameSdkCsharpExampleGodot/script/gen/code";
documentGenerate.setPath(path);
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
}
```
Advantages of SDK Code Generation
1. Helps client-side developers reduce significant workload by eliminating the need to write a large amount of template code.
2. Clear and semantically precise. The generated interaction code clearly defines parameter types and return types.
3. Ensures parameter type safety and clarity in interface methods, effectively avoiding security risks and reducing basic errors during integration.
4. 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.
5. Helps client-side developers abstract away the interaction with the server, allowing them to focus more on the core business logic.
6. Reduces the cognitive load during integration. The code is simple to use, similar to local method calls.
7. Abandons the traditional protocol-based approach in favor of an interface-method-based integration approach.
------
**[other updates]**
```xml
3.25.5
```
---
### 2024-10-28 - v21.19
https://github.com/iohao/ioGame/releases/tag/21.19
**版本更新汇总**
> 1. [core] FlowContext provides the setUserId method to simplify the login operation.
> 2. [broker] Added RingElementSelector load balancing implementation and set it as default to replace RandomElementSelector
> 3. [core] [#386](https://github.com/iohao/ioGame/issues/386) Action supports constructor injection with parameters in Spring
> 4. Simplify the implementation class of ActionParserListener related to ProtoDataCodec. and #386
> 5. perf(i18n): 🐳 [#376](https://github.com/iohao/ioGame/issues/376) cmd check tips
> 6. refactor(external): simplify and improve externalCache
------
**[core]** FlowContext provides the setUserId method to simplify the login operation.
> FlowContext 提供登录方法以简化登录的使用
```java
@ActionController(LoginCmd.cmd)
public class TheLoginAction {
... ...
@ActionMethod(LoginCmd.login)
public UserInfo loginVerify(LoginVerify loginVerify, FlowContext flowContext) {
long userId = ...;
// Deprecated
boolean success = UserIdSettingKit.settingUserId(flowContext, userId);
// now
boolean success = flowContext.setUserId(userId);
return ...;
}
}
```
---
**[core]** [#386](https://github.com/iohao/ioGame/issues/386) Action supports constructor injection with parameters in Spring
> 在 Spring 中,Action 支持构造函数注入
```java
// Action supports constructor injection in Spring.
@Component
@AllArgsConstructor
@ActionController(PersonCmd.cmd)
public class PersonAction {
final PersonService personService;
...
}
```
---
refactor(external): simplify and improve externalCache
> 简化与提升游戏对外服缓存
```java
// create externalCache
private static void extractedExternalCache() {
// Deprecated
DefaultExternalCmdCache externalCmdCache = new DefaultExternalCmdCache();
// now
var externalCmdCache = ExternalCmdCache.of();
}
```
------
**[其他更新]**
```xml
4.1.114.Final
```
------
### 2024-10-09 - v21.18
https://github.com/iohao/ioGame/releases/tag/21.18
**版本更新汇总**
> - [external] [#375](https://github.com/iohao/ioGame/issues/375) Support for lightweight or embedded Linux distributions. 支持轻量级或嵌入式 Linux 发行版。
> - [core] [#376](https://github.com/iohao/ioGame/issues/376) Support i18n, such as logs and internal messages. 框架内的日志、内部消息支持 i18n。
------
**[core]**
[#376](https://github.com/iohao/ioGame/issues/376) Support i18n, such as logs and internal messages. 框架内的日志、内部消息支持 i18n。
```java
public class DemoApplication {
public static void main(String[] args) {
// setting defaultLocale, such as US or CHINA
Locale.setDefault(Locale.US);
Locale.setDefault(Locale.CHINA);
... start ioGame
}
}
```
------
**[其他更新]**
```
2.6.17
```
------
### 2024-09-25 - v21.17
https://github.com/iohao/ioGame/releases/tag/21.17
**版本更新汇总**
> - [core] 简化 TraceIdSupplier 全链路调用日志跟踪默认实现
> - [core] FlowContext 提供用户(玩家)所关联的用户线程执行器信息及虚拟线程执行器信息方法
---
**[core]**
FlowContext 提供用户(玩家)所关联的用户线程执行器信息及虚拟线程执行器信息方法
```java
void testThreadExecutor(FlowContext flowContext) {
// 获取 - 用户(玩家)所关联的用户线程执行器信息及虚拟线程执行器信息
// 用户虚拟线程执行器信息
ThreadExecutor virtualThreadExecutor = flowContext.getVirtualThreadExecutor();
// 用户线程执行器信息
ThreadExecutor threadExecutor = flowContext.getThreadExecutor();
threadExecutor.execute(() -> {
log.info("execute");
});
threadExecutor.executeTry(() -> {
log.info("executeTry");
});
// get Executor
Executor executor = threadExecutor.executor();
}
```
------
### 2024-09-09 - v21.16
https://github.com/iohao/ioGame/releases/tag/21.16
**版本更新汇总**
> - [kit] [#291](https://github.com/iohao/ioGame/issues/291) 增加轻量可控的延时任务
> - [kit] 细分时间日期相关工具。
> - [Archive] [#363](https://github.com/iohao/ioGame/issues/363) light-redis-lock 相关模块
> - [Archive] [#364](https://github.com/iohao/ioGame/issues/364) light-timer-task 相关模块
> - [core] 增加同一个 ActionController 相同的 action 方法名只允许存在一个的检测。
> - [core] Banner 增加启动时的错误数量提示。
> - [core] [#365](https://github.com/iohao/ioGame/issues/365) 支持对接文档生成时,可以根据路由访问权限来控制文档的生成
------
**[kit]**
[#291](https://github.com/iohao/ioGame/issues/291) 增加轻量可控的延时任务
for example
```java
@Test
public void example() {
long timeMillis = System.currentTimeMillis();
DelayTask delayTask = DelayTaskKit.of(() -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("1 - 最终 {} ms 后,执行延时任务", value);
})
.plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时
.task(); // 启动任务
delayTask.plusTimeMillis(500); // 增加 0.5 秒的延时
delayTask.minusTimeMillis(500);// 减少 0.5 秒的延时时间
// 因为 taskId 相同,所以会覆盖之前的延时任务
String taskId = delayTask.getTaskId();
delayTask = DelayTaskKit.of(taskId, () -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("2 - 最终 {} ms 后,执行延时任务", value);
})
.plusTime(Duration.ofSeconds(1)) // 增加 1 秒的延时
.task(); // 启动任务
// 取消延时任务,下面两个方法是等价的
delayTask.cancel();
DelayTaskKit.cancel(taskId);
// 可以通过 taskId 查找该延时任务
Optional optionalDelayTask = DelayTaskKit.optional(taskId);
if (optionalDelayTask.isPresent()) {
var delayTask = optionalDelayTask.get();
}
// 通过 taskId 查找延时任务,存在则执行给定逻辑
DelayTaskKit.ifPresent(taskId, delayTask -> {
delayTask.plusTimeMillis(500); // 增加 0.5 秒的延时时间
});
}
```
------
细分时间日期相关工具。
see com.iohao.game.common.kit.time
------
**[Archive]**
[#363](https://github.com/iohao/ioGame/issues/363) light-redis-lock 相关模块
将 light-redis-lock、light-redis-lock-spring-boot-starter 模块做归档。在过去的时间里,由于一直没有改动这些模块的相关内容,现决定将不再上传到 maven 库中,以节约公共资源。如果你使用了该模块的相关内容,请指定最后一个版本即可。如
```xml
com.iohao.gamelight-redis-lock21.15com.iohao.gamelight-redis-lock-spring-boot-starter21.15
```
------
[#364](https://github.com/iohao/ioGame/issues/364) light-timer-task 相关模块
将 light-timer-task 模块做归档。在过去的时间里,由于一直没有改动这些模块的相关内容;同时,也因为框架内置了类似的功能 #291 。现决定将不再上传到 maven 库中,以节约公共资源。如果你使用了该模块的相关内容,请指定最后一个版本即可。如
```xml
com.iohao.gamelight-timer-task21.15
```
------
**[core]**
[#365](https://github.com/iohao/ioGame/issues/365) 支持对接文档生成时,可以根据路由访问权限来控制文档的生成
生成相关代码的使用及相关文档
- `ExternalGlobalConfig.accessAuthenticationHook`,相关文档路由访问权限控制
- IoGameDocumentHelper,相关文档游戏对接文档生成
for example
```java
public class MyExternalServer {
public static void extractedAccess() {
// https://iohao.github.io/game/docs/external/access_authentication
var accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;
... 省略部分代码
// 添加 - 拒绝玩家访问权限的控制
accessAuthenticationHook.addRejectionCmd(RankCmd.cmd, RankCmd.internalUpdate);
}
}
public class TestGenerate {
... 省略部分代码
public static void main(String[] args) {
// 对外服访问权限控制
MyExternalServer.extractedAccess();
// (复用)设置文档路由访问权限控制
IoGameDocumentHelper.setDocumentAccessAuthentication(ExternalGlobalConfig.accessAuthenticationHook::reject);
// ====== 生成对接文档、生成 proto ======
// generateCsharp();
// generateTypeScript();
// 生成文档
IoGameDocumentHelper.generateDocument();
// .proto 文件生成
// generateProtoFile();
}
}
```
**预览 - 没有做控制前的生成**
```latex
==================== RankAction ====================
路由: 4 - 1 --- 【listRank】 --- 【RankAction:48】【listRank】
方法参数: StringValue 排行类型
方法返回值: ByteValueList 玩家排行名次更新
路由: 4 - 10 --- 【玩家排行名次更新】 --- 【RankAction:60】【internalUpdate】
方法参数: RankUpdate 玩家排行名次更新
方法返回值: void
```
**预览 - 加入了访问控制后的生成**
我们可以看见,路由为 4-10 的 action 方法没有生成到对接文档中。
```latex
==================== RankAction ====================
路由: 4 - 1 --- 【listRank】 --- 【RankAction:48】【listRank】
方法参数: StringValue 排行类型
方法返回值: ByteValueList 玩家排行名次更新
```
提示:除了文档文档的访问权限控制外,还支持 SDK TypeScript、SDK C# ...等客户端代码生成的访问权限控制。
------
**[其他更新]**
```xml
4.1.113.Final
```
------
### 2024-08-26 - v21.15
https://github.com/iohao/ioGame/releases/tag/21.15
**版本更新汇总**
> - [core] [#351](https://github.com/iohao/ioGame/issues/351) 增加 UserProcessor 线程执行器的选择策略扩展
> - [core] [#350](https://github.com/iohao/ioGame/issues/350) 修复请求消息在 Broker 环节乱序的问题
> - [core] [#353](https://github.com/iohao/ioGame/issues/353) 对接文档支持框架内置错误码的生成
> - [core] [#354](https://github.com/iohao/ioGame/issues/354) 日志打印调整
> - [core] [#359](https://github.com/iohao/ioGame/issues/359) [逻辑服-监听] 增加打印其他进程逻辑服的上线与下线信息
> - [core] 优化 ThreadExecutorRegion 相关实现类。
> - [external] UserSession 接口新增 ofRequestMessage 方法,简化玩家在游戏对外服中创建请求对象。
------
**[external]**
UserSession 接口新增 ofRequestMessage 方法,简化玩家在游戏对外服中创建请求对象。 for example
```java
var cmdInfo = CmdInfo.of(1, 1);
RequestMessage request = userSession.ofRequestMessage(cmdInfo);
```
------
**[core]**
[#359](https://github.com/iohao/ioGame/issues/359) [逻辑服-监听] 增加打印其他进程逻辑服的上线与下线信息
```java
public class MyLogicServer extends AbstractBrokerClientStartup {
...
@Override
public BrokerClientBuilder createBrokerClientBuilder() {
BrokerClientBuilder builder = BrokerClient.newBuilder();
...
// 添加监听 - 打印其他进程逻辑服的上线与下线信息
builder.addListener(SimplePrintBrokerClientListener.me());
return builder;
}
}
```
------
[#351](https://github.com/iohao/ioGame/issues/351) 增加 UserProcessor 线程执行器的选择策略扩展
> for example,注意事项:当你的 UserProcessor 做了线程执行器的选择策略扩展,需要重写 CustomSerializer 接口的相关方法。
```java
// 为请求消息开启有序的、多线程处理的优化
IoGameGlobalConfig.enableUserProcessorExecutorSelector();
```
------
### 2024-08-08 - v21.14
https://github.com/iohao/ioGame/releases/tag/21.14
**版本更新汇总**
> - [code quality] 提升代码质量,see [ioGame - Qodana Cloud](https://qodana.cloud/organizations/3k6Pm/teams/zxRGm)
> - [javadoc] 增强相关模块的 javadoc :业务框架、压测与模拟客户端请求、领域事件、Room
> - [core] [#346](https://github.com/iohao/ioGame/issues/346) 业务框架 InOutManager 提供扩展点
> - [core] [#344](https://github.com/iohao/ioGame/issues/344) 登录时,如果 FlowContext 存在 userId 就不请求游戏对外服
> - [broker] fixed [#342](https://github.com/iohao/ioGame/issues/342) 非集群环境下,Broker 断开重启后,逻辑服没有将其重新加入到 BrokerClientManager 中所引发的 NPE。
------
**[core]**
[#346](https://github.com/iohao/ioGame/issues/346) **业务框架 InOutManager 提供扩展点**
在构建器中配置 InOutManager 策略,框架内置了两个实现类,分别是
1. ofAbcAbc :in ABC,out ABC 的顺序,即编排时的顺序。
2. ofPipeline:in ABC,out CBA 的顺序,类似的 netty Pipeline 。(默认策略,如果不做任何设置,将使用该策略)
for example 在构建器中配置 InOutManager 策略
```java
public void config() {
BarSkeletonBuilder builder = ...;
builder.setInOutManager(InOutManager.ofAbcAbc());
builder.setInOutManager(InOutManager.ofPipeline());
}
```
------
### 2024-07-24 - v21.13
https://github.com/iohao/ioGame/releases/tag/21.13
**版本更新汇总**
- [external] [#334](https://github.com/iohao/ioGame/issues/334) 顶号操作 bug,有概率触发并发问题
- [core] FlowContext 新增 createRequestCollectExternalMessage 方法
- [javadoc] 源码 javadoc 增强
------
**[core]**
FlowContext 新增 createRequestCollectExternalMessage 方法,request 与游戏对外服交互。
```java
... ... 省略部分代码
@ActionMethod(ExternalBizRegionCmd.listOnlineUserAll)
public List listOnlineUserAll(FlowContext flowContext) {
// 创建 RequestCollectExternalMessage
var request = flowContext
.createRequestCollectExternalMessage(MyExternalBizCode.onlineUser);
// 访问多个【游戏对外服】
var collectExternalMessage = flowContext
.invokeExternalModuleCollectMessage(request);
return listUserId(collectExternalMessage);
}
```
------
**[其他更新]**
```xml
4.1.112.Final1.18.34
```
------
### 2024-07-08 - v21.12
https://github.com/iohao/ioGame/releases/tag/21.12
**版本更新汇总**
- [light-game-room] [#326](https://github.com/iohao/ioGame/issues/326) GameFlowContext getRoom、getPlayer 方法返回值改成泛型
- [对接文档] [#330](https://github.com/iohao/ioGame/issues/330) 增强,支持对接文档生成与扩展,包括文本文档生成、联调代码生成 ...等
当前版本,为之后生成联调代码做了充分的准备
------
**[light-game-room]**
[#326](https://github.com/iohao/ioGame/issues/326) GameFlowContext getRoom、getPlayer 方法返回值改成泛型
```java
GameFlowContext gameFlowContext = ...;
// FightRoomEntity 是自定义的 Room 对象
// Room、Player 在使用时,不需要强制转换了
FightRoomEntity room = gameFlowContext.getRoom();
FightPlayerEntity player = gameFlowContext.getPlayer();
```
------
**[对接文档]**
[#330](https://github.com/iohao/ioGame/issues/330) 增强,支持对接文档生成与扩展,包括文本文档生成、联调代码生成 ...等。开发者做更多个性化的扩展
在该版本中,我们已经新做了对接文档相关模块;该版本功能更加的强大,使用上也更加的简洁。新版本的对接文档模块,除了能提供文本文档的生成外,还能支持生成与客户端联调的代码、并且是可扩展的。通常,客户端联调代码有:
1. 支持生成 C# 客户端的联调代码,通常用在 Unity、Godot 客户端
2. 支持生成 TypeScript 客户端的联调代码,通常用在 cocos、laya 客户端
```java
public static void main(String[] args) {
// 添加枚举错误码 class,用于生成错误码相关信息
IoGameDocumentHelper.addErrorCodeClass(GameCode.class);
// 添加文档生成器,文本文档
IoGameDocumentHelper.addDocumentGenerate(new TextDocumentGenerate());
// 添加文档生成器,Ts 联调代码生成
IoGameDocumentHelper.addDocumentGenerate(new TypeScriptDocumentGenerate());
// 生成文档
IoGameDocumentHelper.generateDocument();
}
```
上述代码
- 添加了错误码的生成
- 添加了文本文档的生成
- 添加了 Ts 客户端联调代码的生成(包括 action、广播、错误码...相关代码的生成), [SDK TypeScript 客户端代码生成;方便 CocosCeator、或其他支持 TypeScript 的客户端对接。 #329](https://github.com/iohao/ioGame/issues/329)
addDocumentGenerate 是可扩展的,这将意味着开发者可以扩展出 C#、GodotScript、Js ...等不同客户端的联调代码。默认,我们提供了一个文本文档,即 TextDocumentGenerate,如果默认的实现满足不了当下需求,开发者也可以定制个性化的文档,如 json 格式的。
**新增 DocumentGenerate 接口**
开发者可利用该接口进行定制个性化的对接文档,如代码生成 ...等。
```java
/**
* 对接文档生成接口,可扩展不同的实现
*/
public interface DocumentGenerate {
/**
* 生成文档
*
* @param ioGameDocument ioGameDocument
*/
void generate(IoGameDocument ioGameDocument);
}
/**
* 文档相关信息,如 action 相关、广播相关、错误码相关。
*/
@Getter
public final class IoGameDocument {
/** 已经解析好的广播文档 */
List broadcastDocumentList;
/** 已经解析好的错误码文档 */
List errorCodeDocumentList;
/** 已经解析好的 action 文档 */
List actionDocList;
}
```
开发者可以通过实现 DocumentGenerate 接口来扩展不同的文档生成,开发者可以扩展此接口来定制更多个性化的扩展,如
- html 版本的文档。
- json 版本的文档。
- 其他语言的联调文档 ...等。
```java
// 使用示例
private static void test() {
var documentGenerate = new YourDocumentGenerate();
IoGameDocumentHelper.addDocumentGenerate(documentGenerate);
}
```
------
其他:废弃旧版本对接文档相关类 DocActionSend、DocActionSends、ActionDocs、ActionSendDoc、ActionSendDocs、ActionSendDocsRegion、BarSkeletonDoc、BroadcastDoc、BroadcastDocBuilder、ErrorCodeDocs、ErrorCodeDocsRegion。
------
21.10 及之前版本的使用示例(对接文档)
```java
public static void main(String[] args) {
... 省略部分代码
new NettyRunOne()
... ...
.startup();
// 生成对接文档
BarSkeletonDoc.me().buildDoc();
}
```
------
### 2024-06-21 - v21.10
(问题版本)
https://github.com/iohao/ioGame/releases/tag/21.10
------
**版本更新汇总**
- [core] [#315](https://github.com/iohao/ioGame/issues/315) ResponseMessage 增加协议碎片便捷获取,简化跨服调用时的使用
- [core] ActionCommand 增加 containAnnotation、getAnnotation 方法,简化获取 action 相关注解信息的使用。
- [kit] [动态属性] 增加 ifNull 方法,如果动态属性值为 null,则执行给定的操作,否则不执行任何操作。执行给定操作后将得到一个返回值,该返回值会设置到动态属性中。
- [kit] TimeKit 增加 nowLocalDate 方法,可减少 LocalDate 对象的创建;优化 currentTimeMillis 方法的时间更新策略。同时,优化 nowLocalDate、currentTimeMillis 方法,不使用时将不会占用相关资源。
- [EventBus] 分布式事件总线增加 EventBusRunner 接口。EventBus 接口化,方便开发者自定义扩展。fix 订阅者使用自身所关联的 EventBus 处理相关事件。
------
**[core]** [315](https://github.com/iohao/ioGame/issues/315) ResponseMessage 增加协议碎片便捷获取,简化跨服调用时的使用
框架具备协议碎片特性。某些业务中,我们需要跨服访问其他游戏逻辑服,以获取某些业务数据;一些简单的数据,我们可以通过协议碎片来返回,从而避免定义过多的协议。
现为 ResponseMessage 增加协议碎片支持,简化跨服调用时的使用,新增的方法如下
```java
public void test() {
ResponseMessage responseMessage = ...;
// object
responseMessage.getValue(Student.class);
List listValue = responseMessage.listValue(Student.class);
// int
int intValue = responseMessage.getInt();
List listInt = responseMessage.listInt();
// long
long longValue = responseMessage.getLong();
List listLong = responseMessage.listLong();
// String
String stringValue = responseMessage.getString();
List listString = responseMessage.listString();
// boolean
boolean boolValue = responseMessage.getBoolean();
List listBoolean = responseMessage.listBoolean();
}
```
示例说明
- HomeAction 是 【Home 游戏逻辑服】提供的 action
- UserAction 是 【User 游戏逻辑服】提供的 action
两个逻辑服的交互如下,UserAction 使用跨服方式调用了【Home 游戏逻辑服】的几个方法,并通过 responseMessage 的协议碎片支持,简化跨服调用时的使用。
示例中演示了 string、string list、object list 的简化使用(协议碎片获取时的简化使用)。
```java
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class Student {
String name;
}
// home 游戏逻辑服提供的 action
public class HomeAction {
@ActionMethod(HomeCmd.name)
public String name() {
return "a";
}
@ActionMethod(HomeCmd.listName)
public List listName() {
return List.of("a", "b");
}
@ActionMethod(HomeCmd.listStudent)
public List listStudent() {
Student student = new Student();
student.name = "a";
Student student2 = new Student();
student2.name = "b";
return List.of(student, student2);
}
}
@ActionController(UserCmd.cmd)
public class UserAction {
@ActionMethod(UserCmd.userSleep)
public void userSleep(FlowContext flowContext) {
flowContext.invokeModuleMessageAsync(HomeCmd.of(HomeCmd.name), responseMessage -> {
String name = responseMessage.getString();
log.info("{}", name);
});
flowContext.invokeModuleMessageAsync(HomeCmd.of(HomeCmd.listName), responseMessage -> {
var listName = responseMessage.listString();
log.info("{}", listName);
});
flowContext.invokeModuleMessageAsync(HomeCmd.of(HomeCmd.listStudent), responseMessage -> {
List studentList = responseMessage.listValue(Student.class);
log.info("{}", studentList);
});
}
}
```
------
**[core]** ActionCommand 增加 containAnnotation、getAnnotation 方法,简化获取 action 相关注解信息的使用。
```java
ActionCommand actionCommand = flowContext.getActionCommand();
bool contain = actionCommand.containAnnotation(DisableDebugInout.class);
var annotation = actionCommand.getAnnotation(DisableDebugInout.class);
```
------
**[EventBus] 分布式事件总线**
1. [增强扩展] 将抽象类 AbstractEventBusRunner 标记为过时的,由接口 EventBusRunner 代替。
2. [增强扩展] 分布式事件总线 EventBus 接口化,方便开发者自定义扩展。增加[总线相关的 javadoc](https://iohao.github.io/javadoc/com/iohao/game/action/skeleton/eventbus/package-summary.html)。
3. [fix] 订阅者使用自身所关联的 EventBus 处理相关事件。
关于 fix 订阅者使用自身所关联的 EventBus 处理相关事件,在此之前可能引发 bug 的场景如下
1. 【游戏逻辑服 A】 发布事件。
2. 【游戏逻辑服 B】 订阅者接收事件并处理,在处理过程中又调用了【游戏逻辑服 A】 某个 action 方法。
该业务场景,会在多服单进程下会引发调用超时,但在多服多进程下则不会超时。
------
**[kit] TimeKit**
增强 TimeKit 增加 nowLocalDate 方法,可减少 LocalDate 对象的创建;
优化 currentTimeMillis 方法的时间更新策略。
优化 nowLocalDate、currentTimeMillis 不使用时将不会占用相关资源。
```csharp
@Test
public void test() {
long millis = TimeKit.currentTimeMillis();
Assert.assertTrue(millis > 0);
LocalDate localDate = TimeKit.nowLocalDate();
Assert.assertTrue(localDate.isEqual(LocalDate.now()));
}
```
------
**[kit] 动态属性**
[动态属性] 增加 ifNull 方法,如果动态属性值为 null,则执行给定的操作,否则不执行任何操作。执行给定操作后将得到一个返回值,该返回值会设置到动态属性中。
```csharp
public class AttrOptionDynamicTest {
// 动态属性 key
AttrOption attrCatOption = AttrOption.valueOf("AttrCat");
@Test
public void ifNull() {
var myAttrOptions = new MyAttrOptions();
Assert.assertNull(myAttrOptions.option(attrCatOption));
// 如果 catAttrOption 属性为 null,则创建 AttrCat 对象,并赋值到属性中
myAttrOptions.ifNull(attrCatOption, AttrCat::new);
Assert.assertNotNull(myAttrOptions.option(attrCatOption));
}
private static class AttrCat {
String name;
}
@Getter
private static class MyAttrOptions implements AttrOptionDynamic {
final AttrOptions options = new AttrOptions();
}
}
```
------
**[其他 - 相关库升级]**
4.1.111.Final4.0.52.4.23
---
### 2024-06-03 - v21.9
(问题版本)
https://github.com/iohao/ioGame/releases/tag/21.9
---
**版本更新汇总**
- [core] [#294](https://github.com/iohao/ioGame/issues/294) 增加范围内的广播接口 RangeBroadcaster,业务参数支持基础类型的简化使用
- [core-对接文档] [#293](https://github.com/iohao/ioGame/issues/293) 广播文档构建器支持对参数的单独描述
- [light-game-room] [#297](https://github.com/iohao/ioGame/issues/297) 模拟系统创建房间,RoomCreateContext 的使用
- [light-game-room] [#298](https://github.com/iohao/ioGame/issues/298) 模拟系统创建房间,GameFlowContext 的使用
- [core] [#301](https://github.com/iohao/ioGame/issues/301) FlowContext 更新元信息后,需要立即生效(跨服调用时)
- [内置 kit] 开放 TaskListener 接口
- 为 SimpleRoom aggregationContext 属性提供默认值,移除 RoomCreateContext 接口的 getAggregationContext 方法,以免产生误导。
---
**[light-game-room]**
为 SimpleRoom aggregationContext 属性提供默认值
[#297](https://github.com/iohao/ioGame/issues/297),模拟系统创建房间,RoomCreateContext 的使用
> 移除 RoomCreateContext 接口的 getAggregationContext 方法,以免产生误导。
>
> RoomCreateContext 增加默认重载
```java
RoomCreateContext.of(); // 无房间创建者,通常表示系统创建
RoomCreateContext.of(userId); // 房间创建者为 userId
```
[#298](https://github.com/iohao/ioGame/issues/298) 模拟系统创建房间,GameFlowContext 的使用
```java
public void test() {
Room room = ...;
GameFlowContext context = GameFlowContext.of(room);
... 省略部分代码
}
```
---
**[core]**
[#294](https://github.com/iohao/ioGame/issues/294) 增加范围内的广播接口 RangeBroadcaster,业务参数支持基础类型的简化使用
```java
public void testRangeBroadcaster(FlowContext flowContext) {
// ------------ object ------------
// 广播 object
DemoBroadcastMessage message = new DemoBroadcastMessage();
message.msg = "helloBroadcast --- 1";
RangeBroadcaster.of(flowContext)
.setResponseMessage(cmdInfo, message);
// 广播 object list
List messageList = List.of(message);
RangeBroadcaster.of(flowContext)
.setResponseMessageList(cmdInfo, messageList);
// ------------ int ------------
// 广播 int
int intValue = 1;
RangeBroadcaster.of(flowContext)
.setResponseMessage(cmdInfo, intValue);
// 广播 int list
List intValueList = List.of(1, 2);
RangeBroadcaster.of(flowContext)
.setResponseMessageIntList(cmdInfo, intValueList);
// ------------ long ------------
// 广播 long
long longValue = 1L;
RangeBroadcaster.of(flowContext)
.setResponseMessage(cmdInfo, longValue);
// 广播 long list
List longValueList = List.of(1L, 2L);
RangeBroadcaster.of(flowContext)
.setResponseMessageLongList(cmdInfo, longValueList);
// ------------ String ------------
// 广播 String
String stringValue = "1";
RangeBroadcaster.of(flowContext)
.setResponseMessage(cmdInfo, stringValue);
// 广播 String list
List stringValueList = List.of("1L", "2L");
RangeBroadcaster.of(flowContext)
.setResponseMessageStringList(cmdInfo, stringValueList);
// ------------ boolean ------------
// 广播 boolean
boolean boolValue = true;
RangeBroadcaster.of(flowContext)
.setResponseMessage(cmdInfo, boolValue);
// 广播 boolean list
List boolValueList = List.of(true, false);
RangeBroadcaster.of(flowContext)
.setResponseMessageBoolList(cmdInfo, boolValueList);
}
```
[#301](https://github.com/iohao/ioGame/issues/301) FlowContext 更新元信息后,需要立即生效(跨服调用时)
> 在此之前,更新元信息后,并不会将元信息同步到 FlowContext 中,只会将元信息同步到游戏对外服中;所以在更新元信息后,紧接着执行跨服调用是不能获取新的元信息内容的。
>
> 当前 issues 会对这部分做增强,也就是在更新元信息后,会将元信息同步到 FlowContext 中;这样,在后续的跨服调用中也能获取到最新的元信息。
```java
void test1(FlowContext flowContext) {
// 获取元信息
MyAttachment attachment = flowContext.getAttachment(MyAttachment.class);
attachment.nickname = "渔民小镇";
// [同步]更新 - 将元信息同步到玩家所在的游戏对外服中
flowContext.updateAttachment(attachment);
// 跨服请求
CmdInfo helloCmdInfo = CmdInfo.of(1, 1);
flowContext.invokeModuleMessage(helloCmdInfo);
}
@ActionController(1)
public class DemoFightAction {
@ActionMethod(1)
void hello(FlowContext flowContext) {
// 可以得到最新的元信息
MyAttachment attachment = flowContext.getAttachment(MyAttachment.class);
log.info("{}", attachment.nickname);
}
}
```
[#293](https://github.com/iohao/ioGame/issues/293) 广播文档构建器支持对参数的单独描述
```java
private void extractedDco(BarSkeletonBuilder builder) {
// UserCmd
builder.addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.enterSquare))
.setDataClass(SquarePlayer.class)
.setDescription("新玩家加入房间,给房间内的其他玩家广播")
).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.offline))
.setDataClass(LongValue.class, "userId")
.setDescription("有玩家下线了")
);
}
```
> 下面是生成后的对接文档预览
```text
==================== FightHallAction 大厅(类似地图) ====================
路由: 1 - 2 --- 【进入大厅】 --- 【FightHallAction:94】【enterSquare】
方法参数: EnterSquare enterSquare 进入大厅
方法返回值: ByteValueList 所有玩家
广播推送: SquarePlayer ,(新玩家加入房间,给房间内的其他玩家广播)
路由: 1 - 5 --- 【玩家下线】 --- 【FightHallAction:154】【offline】
方法返回值: void
广播推送: LongValue userId,(有玩家下线了)
```
**[内置 kit]**
开放 TaskListener 接口,TaskListener 是 TaskKit 相关的任务监听接口。
TaskListener 任务监听回调,使用场景有:一次性延时任务、任务调度、轻量可控的延时任务、轻量的定时入库辅助功能 ...等其他扩展场景。这些使用场景都有一个共同特点,即监听回调。接口提供了 4 个方法,如下
1. CommonTaskListener.onUpdate(),监听回调
2. CommonTaskListener.triggerUpdate(),是否触发 CommonTaskListener.onUpdate() 监听回调方法
3. CommonTaskListener.onException(Throwable) ,异常回调。在执行 CommonTaskListener.triggerUpdate() 和 CommonTaskListener.onUpdate() 方法时,如果触发了异常,异常将被该方法捕获。
4. CommonTaskListener.getExecutor(),指定执行器来执行上述方法,目的是不占用业务线程。
---
### 2024-05-19 - v21.8
https://github.com/iohao/ioGame/releases/tag/21.8
---
**版本更新汇总**
- [light-game-room] [#278](https://github.com/iohao/ioGame/issues/278) 桌游类、房间类游戏的扩展模块,简化与规范化房间管理相关的、开始游戏流程相关的、玩法操作相关的相关扩展
- [core] [#290](https://github.com/iohao/ioGame/issues/290) 新增广播文档构建器,简化生成广播对接文档
- [示例集合整理] 将 SimpleExample、SpringBootExample、ioGameWeb2Game、fxglSimpleGame FXGL + netty合并成一个示例项目。
---
**[core]**
[#290](https://github.com/iohao/ioGame/issues/290) 新增广播文档构建器,简化生成广播对接文档
下面是使用示例
```java
public class MyLogicServer extends AbstractBrokerClientStartup {
@Override
public BarSkeleton createBarSkeleton() {
// 业务框架构建器
BarSkeletonBuilder builder = ...
// 错误码、广播、推送对接文档生成
extractedDco(builder);
return builder.build();
}
private void extractedDco(BarSkeletonBuilder builder) {
// 错误码
Arrays.stream(GameCode.values()).forEach(builder::addMsgExceptionInfo);
// UserCmd
builder.addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.enterSquare))
.setDataClass(SquarePlayer.class)
.setDescription("新玩家加入房间,给房间内的其他玩家广播")
).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.move))
.setDataClass(SquarePlayerMove.class)
.setDescription("其他玩家的移动")
).addBroadcastDoc(BroadcastDoc.newBuilder(UserCmd.of(UserCmd.offline))
.setDataClass(LongValue.class)
.setDescription("有玩家下线了。userId")
);
// room
builder.addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.roomUpdateBroadcast))
.setDataClass(FightRoomNotice.class)
.setDescription("房间更新通知")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.playerEnterRoomBroadcast))
.setDataClass(FightPlayer.class)
.setDescription("有新玩家加入房间")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.enterRoom))
.setDataClass(FightEnterRoom.class)
.setDescription("玩家自己进入房间")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.dissolveRoomBroadcast))
.setDescription("解散房间")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.quitRoom))
.setDataClass(LongValue.class)
.setDescription("有玩家退出房间了。userId")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.ready))
.setDataClass(PlayerReady.class)
.setDescription("有玩家准备或取消准备了")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.nextRoundBroadcast))
.setDataClass(IntValue.class)
.setDescription("对局开始,通知玩家开始选择。round 当前对局数")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.operationBroadcast))
.setDataClass(LongValue.class)
.setDescription("通知其他玩家,有玩家做了选择。userId")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.littleSettleBroadcast))
.setDataClassList(FightRoundPlayerScore.class)
.setDescription("广播玩家对局分数")
).addBroadcastDoc(BroadcastDoc.newBuilder(RoomCmd.of(RoomCmd.gameOverBroadcast))
.setDescription("游戏结束")
);
}
}
```
下面是生成后的对接文档预览
```text
==================== FightHallAction 大厅(类似地图) ====================
路由: 1 - 1 --- 【登录】 --- 【FightHallAction:67】【loginVerify】
方法参数: LoginVerify loginVerify 登录验证
方法返回值: UserInfo 玩家信息
路由: 1 - 2 --- 【进入大厅】 --- 【FightHallAction:95】【enterSquare】
方法参数: EnterSquare enterSquare 进入大厅
方法返回值: ByteValueList 所有玩家
广播推送: SquarePlayer 新玩家加入房间,给房间内的其他玩家广播
路由: 1 - 4 --- 【玩家移动】 --- 【FightHallAction:131】【move】
方法参数: SquarePlayerMove squarePlayerMove 玩家移动
方法返回值: void
广播推送: SquarePlayerMove 其他玩家的移动
路由: 1 - 5 --- 【玩家下线】 --- 【FightHallAction:155】【offline】
方法返回值: void
广播推送: LongValue 有玩家下线了。userId
==================== FightRoomAction ====================
路由: 2 - 1 --- 【玩家创建新房间】 --- 【FightRoomAction:63】【createRoom】
方法返回值: void
路由: 2 - 2 --- 【玩家进入房间】 --- 【FightRoomAction:96】【enterRoom】
方法参数: LongValue roomId 房间号
方法返回值: void 房间信息
广播推送: FightEnterRoom 玩家自己进入房间
路由: 2 - 3 --- 【玩家退出房间】 --- 【FightRoomAction:120】【quitRoom】
方法返回值: void
广播推送: LongValue 有玩家退出房间了。userId
路由: 2 - 4 --- 【玩家准备】 --- 【FightRoomAction:146】【ready】
方法参数: BoolValue ready true 表示准备,false 则是取消准备
方法返回值: void
广播推送: PlayerReady 有玩家准备或取消准备了
路由: 2 - 5 --- 【房间列表】 --- 【FightRoomAction:222】【listRoom】
方法返回值: ByteValueList 房间列表
路由: 2 - 6 --- 【玩家在游戏中的操作】 --- 【FightRoomAction:191】【operation】
方法参数: FightOperationCommand command 玩家操作数据
方法返回值: void
路由: 2 - 7 --- 【开始游戏】 --- 【FightRoomAction:162】【startGame】
方法返回值: void
==================== 其它广播推送 ====================
路由: 2 - 51 --- 广播推送: FightRoomNotice (房间更新通知)
路由: 2 - 50 --- 广播推送: FightPlayer (有新玩家加入房间)
路由: 2 - 52 --- 广播推送: IntValue (对局开始,通知玩家开始选择。round 当前对局数)
路由: 2 - 53 --- 广播推送: LongValue (通知其他玩家,有玩家做了选择。userId)
路由: 2 - 56 --- 广播推送: none (解散房间)
路由: 2 - 54 --- 广播推送: ByteValueList (广播玩家对局分数)
路由: 2 - 55 --- 广播推送: none (游戏结束)
==================== 错误码 ====================
-1008 : 绑定的游戏逻辑服不存在
-1007 : 强制玩家下线
-1006 : 数据不存在
-1005 : class 不存在
-1004 : 请先登录
-1003 : 心跳超时相关
-1002 : 路由错误
-1001 : 参数验错误
-1000 : 系统其它错误
1 : 玩家在房间里
3 : 房间不存在
4 : 非法操作
6 : 开始游戏需要的最小人数不足
7 : 请等待其他玩家准备
8 : 房间空间不足,人数已满
```
---
**[light-game-room]**
[#278](https://github.com/iohao/ioGame/issues/278) 桌游类、房间类游戏的扩展模块,简化与规范化房间管理相关的、开始游戏流程相关的、玩法操作相关的相关扩展
light-game-room 房间,是 ioGame 提供的一个轻量小部件 - 可按需选择的模块。
> **light-game-room + 领域事件 + 内置 Kit = 轻松搞定桌游类游戏**
该模块是桌游类、房间类游戏的解决方案。比较适合桌游类、房间类的游戏基础搭建,基于该模型可以做一些如,炉石传说、三国杀、斗地主、麻将 ...等类似的桌游。或者说只要是房间类的游戏,该模型都适用。比如,CS、泡泡堂、飞行棋、坦克大战 ...等。
如果你计划做一些桌游类的游戏,那么推荐你基于该模块做扩展。该模块遵循面向对象的设计原则,没有强耦合,可扩展性强。且帮助开发者屏蔽了很多重复性的工作,并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。
**主要解决的问题与职责**
桌游、房间类的游戏在功能职责上可以分为 3 大类,分别是
1. **房间管理相关的**
1. 管理着所有的房间、查询房间列表、房间的添加、房间的删除、房间与玩家之间的关联、房间查找(通过 roomId 查找、通过 userId 查找)。
1. **开始游戏流程相关的**
1. 通常桌游、房间类的游戏都有一些固定的流程,如创建房间、玩家进入房间、玩家退出房间、解散房间、玩家准备、开始游戏 ...等。
2. 开始游戏时,需要做开始前的验证,如房间内的玩家是否符足够 ...等,当一切符合业务时,才是真正的开始游戏。
1. **玩法操作相关的**
1. 游戏开始后,由于不同游戏之间的具体操作是不相同的。如坦克的射击,炉石的战前选牌、出牌,麻将的吃、碰、杠、过、胡,回合制游戏的普攻、防御、技能 ...等。
2. 由于玩法操作的不同,所以我们的玩法操作需要是可扩展的,并用于处理具体的玩法操作。同时这种扩展方式更符合单一职责,使得我们后续的扩展与维护成本更低。
以上功能职责(房间管理相关、流程相关、玩法操作相关)属于相对通用的功能。如果每款游戏都重复的做这些工作,除了枯燥之外,还将浪费巨大的人力成本。
而当前模块则能很好的帮助开发者屏蔽这些重复性的工作,并可为项目中的功能模块结构、开发流程等进行清晰的组织定义,减少了后续的项目维护成本。更重要的是有相关文档,将来当你的团队有新进成员时,可以快速的上手。
---
**room 实战简介**
文档中,我们基于该 room 模块做一个实战示例,该示例整体比较简单,多名玩家在房间里猜拳(石头、剪刀、布)得分。实战示例包括了前后端,前端使用 [FXGL](https://github.com/almasB/FXGL) 引擎,这样开发者在学习时,只需 JDK 环境就可以了,而不需要安装更多的环境。启动游戏后玩家会将加入大厅(类似地图),多名玩家相互可见,并且玩家可以在大厅内移动。
---
[示例集合整理]
| github | gitee |
| ---------------------------------------------------------- | --------------------------------------------------------- |
| [ioGame 示例集合](https://github.com/iohao/ioGameExamples) | [ioGame 示例集合](https://gitee.com/iohao/ioGameExamples) |
---
### 2024-05-11 - v21.7
https://github.com/iohao/ioGame/releases/tag/21.7
**版本更新汇总**
1. [core] [#112](https://github.com/iohao/ioGame/issues/112) protobuf 协议类添加检测,通过 action 构建时的监听器实现
2. [core] [#272](https://github.com/iohao/ioGame/issues/272) 业务框架 - 提供 action 构建时的监听回调
3. [core] [#274](https://github.com/iohao/ioGame/issues/274) 优化、提速 - 预生成 jprotobuf 协议类的代理,通过 action 构建时的监听器实现
4. [broker] fix [#277](https://github.com/iohao/ioGame/issues/277) 、[#280](https://github.com/iohao/ioGame/issues/280) 偶现 BrokerClientType 为空
5. [external] [#271](https://github.com/iohao/ioGame/issues/271) 游戏对外服 - 内置与可选 handler - log 相关的打印(触发异常、断开连接时)
6. [room] 简化命名: AbstractPlayer --> Player、AbstractRoom --> Room
7. 其他优化:预先生成游戏对外服统一协议的代理类及内置的协议碎片相关代理类,优化 action 参数解析
**[external]**
[#271](https://github.com/iohao/ioGame/issues/271) 游戏对外服 - 内置与可选 handler - log 相关的打印(触发异常、断开连接时)
**[core]**
[#272](https://github.com/iohao/ioGame/issues/272) 业务框架 - 提供 action 构建时的监听回调
开发者可以利用 ActionParserListener 接口来观察 action 构建过程,或者做一些额外的扩展。
扩展示例参考
```java
// 简单打印
public final class YourActionParserListener implements ActionParserListener {
@Override
public void onActionCommand(ActionParserContext context) {
ActionCommand actionCommand = context.getActionCommand();
log.info(actionCommand);
}
}
void test() {
BarSkeletonBuilder builder = ...;
builder.addActionParserListener(new YourActionParserListener());
}
```
[#112](https://github.com/iohao/ioGame/issues/112) protobuf 协议类添加检测,通过 action 构建时的监听器实现
如果当前使用的编解码器为 ProtoDataCodec 时,当 action 的参数或返回值的类没有添加 ProtobufClass 注解时(通常是忘记添加),给予一些警告提示。
```java
// 该协议类没有添加 ProtobufClass 注解
class Bird {
public String name;
}
@ActionController(1)
public class MyAction {
@ActionMethod(1)
public Bird testObject() {
return new Bird();
}
}
======== 注意,协议类没有添加 ProtobufClass 注解 ========
class com.iohao.game.action.skeleton.core.action.Bird
```
[#274](https://github.com/iohao/ioGame/issues/274) 优化、提速 - 预生成 jprotobuf 协议类的代理,通过 action 构建时的监听器实现
如果当前使用的编解码器为 ProtoDataCodec 时,会在启动时就预先生成好 jprotobuf 协议类对应的代理类(用于 .proto 相关的 编码、解码),而不必等到用时在创建该代理类。从而达到整体优化提速的效果。
在此之前,在没做其他设置的情况下,首次访问 action 时,如果参数使用的 jprotobuf 协议类,那么在解码该参数时,会通过 `ProtobufProxy.create` 来创建对应的代理类(类似 .proto 相关的 编码、解码)。之后再访问时,才会从缓存中取到对应的代理类。
该优化默认开启,开发者可以不需要使用与配置跟 jprotobuf-precompile-plugin 插件相关的了。
已经预先生成的代理类有
- 游戏对外服统一协议 ExternalMessage
- 所有开发者定义的 action 的方法参数及返回值
- 解决协议碎片相关,如 int、int list、String、String list、long、long list、ByteValueList ...等
**[room]**
简化命名: AbstractPlayer --> Player、AbstractRoom --> Room
**其他优化**
优化 action 参数解析
---
### 2024-04-23 - v21.6
https://github.com/iohao/ioGame/releases/tag/21.6
**版本更新汇总**
1. [#264](https://github.com/iohao/ioGame/issues/264) 新增属性值变更监听特性
2. 模拟客户端新增与服务器断开连接的方法。模拟客户端新增是否活跃的状态属性。
3. [#265](https://github.com/iohao/ioGame/issues/265) 从游戏对外服中获取玩家相关数据 - 模拟玩家请求。
4. 任务相关:TaskListener 接口增加异常回调方法,用于接收异常信息;当 triggerUpdate 或 onUpdate 方法抛出异常时,将会传递到该回调方法中。
5. [#266](https://github.com/iohao/ioGame/issues/266) 新增 RangeBroadcast 范围内的广播功能,这个范围指的是,可指定某些用户进行广播。
6. AbstractRoom 增加 ifPlayerExist、ifPlayerNotExist 方法。
------
**属性监听特性**
[#264](https://github.com/iohao/ioGame/issues/264) 新增属性值变更监听特性
属性可添加监听器,当某些属性值的发生变化时,触发监听器。
**使用场景举例**
比如玩家的血量低于一定值时,需要触发无敌状态;此时,我们就可以监听玩家的血量,并在该属性上添加一个对应的监听器来观察血量的变化,当达到预期值时就触发对应的业务。
类似的使用场景还有很多,这里就不过多的举例了。属性监听的特点在于属性变化后会触发监听器。
**属性监听特点**
- 可为属性添加监听器,用于观察属性值的变化。
- 属性可以添加多个监听器。
- 属性的监听器可以移除。
**框架已经内置了几个属性实现类,分别是:**
- IntegerProperty
- LongProperty
- StringProperty
- BooleanProperty
- ObjectProperty
------
for example - 添加监听器
BooleanProperty
当 BooleanProperty 对象的值发生改变时,触发监听器。
```java
var property = new BooleanProperty();
// 添加一个监听器。
property.addListener((observable, oldValue, newValue) -> {
log.info("oldValue:{}, newValue:{}", oldValue, newValue);
});
property.get(); // value is false
property.set(true); // 值变更时,将会触发监听器
property.get(); // value is true
```
IntegerProperty
当 IntegerProperty 对象的值发生改变时,触发监听器。
```java
var property = new IntegerProperty();
// add listener monitor property object
property.addListener((observable, oldValue, newValue) -> {
log.info("oldValue:{}, newValue:{}", oldValue, newValue);
});
property.get(); // value is 0
property.set(22); // When the value changes,listeners are triggered
property.get(); // value is 22
property.increment(); // value is 23. will trigger listeners
```
------
for example - 移除监听器
下面这个示例,我们将 property 初始值设置为 10,随后添加了一个监听器;当监听器观察到新值为 9 时,就从 observable 中移除自己(这个自己指的是监听器本身),而 observable 则是 IntegerProperty。
```java
@Test
public void remove1() {
IntegerProperty property = new IntegerProperty(10);
// 添加一个监听器
property.addListener(new PropertyChangeListener<>() {
@Override
public void changed(PropertyValueObservable extends Number> observable, Number oldValue, Number newValue) {
log.info("1 - newValue : {}", newValue);
if (newValue.intValue() == 9) {
// 移除当前监听器
observable.removeListener(this);
}
}
});
property.decrement(); // value 是 9,并触发监听器
property.decrement(); // value 是 8,由于监听器已经移除,所以不会触发任何事件。
}
```
下面的示例中,我们定义了一个监听器类 OnePropertyChangeListener 并实现了 PropertyChangeListener 监听器接口。示例中,我们通过 OnePropertyChangeListener 对象的引用来移除监听器。
```java
@Test
public void remove2() {
// 监听器
OnePropertyChangeListener onePropertyChangeListener = new OnePropertyChangeListener();
// 属性
IntegerProperty property = new IntegerProperty();
// 添加监听器
property.addListener(onePropertyChangeListener);
property.increment(); // value == 1,并触发监听器
property.removeListener(onePropertyChangeListener); // 移除监听器
property.increment(); // value == 2,由于监听器已经移除,所以不会触发任何事件。
}
// 自定义的监听器
class OnePropertyChangeListener implements PropertyChangeListener {
@Override
public void changed(PropertyValueObservable extends Number> observable, Number oldValue, Number newValue) {
log.info("oldValue:{}, newValue:{}, observable:{}", oldValue, newValue, observable);
}
}
```
------
**属性监听 - 小结**
属性监听在使用上是简单的,如果你的业务中**有关于属性变化后需要触发某些事件的**,可以考虑引用该特性。框架为 int、long、boolean、Object、String 等基础类型提供了对应的属性监听。
属性监听特性支持添加多个监听器,支持移除监听器。
------
**模拟客户端相关**
- 模拟客户端新增与服务器断开连接的方法。
- 模拟客户端新增是否活跃的状态属性。
```java
ClientUser clientUser = ...;
// 是否活跃,true 表示玩家活跃
clientUser.isActive();
// 关闭模拟客户端连接
clientUser.getClientUserChannel().closeChannel();
```
------
**访问游戏对外服与扩展相关**
RequestCollectExternalMessage 增加 userId 字段。
[#265](https://github.com/iohao/ioGame/issues/265) 模拟玩家请求时 - 从游戏对外服中获取在线玩家相关数据
新增 UserHeadMetadataExternalBizRegion,从用户(玩家)所在游戏对外服中获取用户自身的数据,如用户所绑定的游戏逻辑服、元信息 ...等
```java
@Slf4j
@RestController
@RequestMapping("other")
public class OtherController {
static final AtomicLong msgId = GameManagerController.msgId;
/** 为了方便测试,这里指定一个 userId 来模拟玩家 */
static final long userId = GameManagerController.userId;
@GetMapping("/notice")
public String notice() {
log.info("other notice");
// 使用协议碎片特性 https://iohao.github.io/game/docs/manual/protocol_fragment
StringValue data = StringValue.of("other GM web msg " + msgId.incrementAndGet());
// 模拟请求 : 路由 - 业务数据
RequestMessage requestMessage = BarMessageKit.createRequestMessage(ExchangeCmd.of(ExchangeCmd.notice), data);
// 设置需要模拟的玩家
HeadMetadata headMetadata = requestMessage.getHeadMetadata();
headMetadata.setUserId(userId);
// 从游戏对外服中获取一些用户(玩家的)自身的数据,如元信息、所绑定的游戏逻辑服 ...等
Optional headMetadataOptional = ExternalCommunicationKit.employHeadMetadata(requestMessage);
if (headMetadataOptional.isPresent()) {
// 发起模拟请求
extractedRequestLogic(requestMessage);
// 打印从游戏对外服获取的元信息
byte[] attachmentData = headMetadata.getAttachmentData();
ExchangeAttachment attachment = DataCodecKit.decode(attachmentData, ExchangeAttachment.class);
return "other notice 玩家的元信息: %s - %s".formatted(attachment, msgId.get());
} else {
return "other notice 玩家 %s 不在线,无法获取玩家的元信息 - %s".formatted(userId, msgId.get());
}
}
private void extractedRequestLogic(RequestMessage requestMessage) {
// 向逻辑服发送请求,该模拟请求具备了玩家的元信息。
BrokerClient brokerClient = MyKit.brokerClient;
InvokeModuleContext invokeModuleContext = brokerClient.getInvokeModuleContext();
invokeModuleContext.invokeModuleVoidMessage(requestMessage);
}
}
```
------
**任务工具相关**
TaskListener 接口增加异常回调方法 `void onException(Throwable e)`,用于接收异常信息;当 triggerUpdate 或 onUpdate 方法抛出异常时,将会传递到该回调方法中。
```java
@Test
public void testException() throws InterruptedException {
AtomicBoolean hasEx = new AtomicBoolean(false);
TaskKit.runOnce(new OnceTaskListener() {
@Override
public void onUpdate() {
// 模拟一个业务异常
throw new RuntimeException("hello exception");
}
@Override
public void onException(Throwable e) {
hasEx.set(true);
// 触发异常后,将来到这里
log.error(e.getMessage(), e);
}
}, 10, TimeUnit.MILLISECONDS);
TimeUnit.MILLISECONDS.sleep(200);
Assert.assertTrue(hasEx.get()); // true
}
```
------
**业务框架相关 - [common-core]**
[#266](https://github.com/iohao/ioGame/issues/266) 新增 RangeBroadcast 范围内的广播功能,这个范围指的是,可指定某些用户进行广播。
在执行广播前,开发者可以自定义业务逻辑,如
- 添加一些需要广播的用户
- 删除一些不需要接收广播的用户
- 可通过重写 logic、trick 方法来做一些额外扩展
```java
// example - 1
new RangeBroadcast(flowContext)
// 需要广播的数据
.setResponseMessage(responseMessage)
// 添加需要接收广播的用户
.addUserId(1)
.addUserId(2)
.addUserId(List.of(3L, 4L, 5L))
// 排除一些用户,被排除的用户将不会接收到广播
.removeUserId(1)
// 执行广播
.execute();
// example - 2
new RangeBroadcast(flowContext)
// 需要广播的数据
.setResponseMessage(cmdInfo, playerReady)
// 添加需要接收广播的用户
.addUserId(1)
// 执行广播
.execute();
```
------
**[light-game-room] 房间模块**
- 移除 AbstractRoom broadcast 系列方法,开发者可使用 RoomBroadcastFlowContext 接口实现旧的兼容。
- 移除 AbstractRoom createSend 方法,开发者可使用 ofRangeBroadcast 系列来代替。AbstractRoom 新增 RoomBroadcastEnhance,实现房间内的广播增强,该系列在语义上更清晰。
```java
final RoomService roomService = ...;
@ActionMethod(RoomCmd.ready)
public void ready(boolean ready, FlowContext flowContext) {
long userId = flowContext.getUserId();
// 得到玩家所在的房间
AbstractRoom room = this.roomService.getRoomByUserId(userId);
// 准备
PlayerReady playerReady = new PlayerReady();
playerReady.userId = userId;
playerReady.ready = ready;
// 通知房间内的所有玩家,有玩家准备或取消准备了
room.ofRangeBroadcast(flowContext)
// 响应数据(路由、业务数据)
.setResponseMessage(flowContext.getCmdInfo(), playerReady)
.execute();
}
// 准备或取消准备
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class PlayerReady {
/** 当前操作的玩家 userId */
long userId;
/** true 表示准备 */
boolean ready;
}
```
------
**AbstractRoom 增加 ifPlayerExist、ifPlayerNotExist 方法。**
**ifPlayerExist 方法**
如果玩家在房间内,就执行给定的操作,否则不执行任何操作。
```java
RoomService roomService = ...;
AbstractRoom room = ...;
// 如果玩家不在房间内,就创建一个玩家,并让玩家加入房间
room.ifPlayerNotExist(userId, () -> {
// 玩家加入房间
FightPlayerEntity newPlayer = new FightPlayerEntity();
newPlayer.setId(userId);
this.roomService.addPlayer(room, newPlayer);
});
```
**ifPlayerNotExist 方法**
如果玩家不在房间内,就执行给定的操作,否则不执行任何操作。
```java
AbstractRoom room = ...;
// 有新玩家加入房间,通知其他玩家
room.ifPlayerExist(userId, (FightPlayerEntity playerEntity) -> {
FightPlayer fightPlayer = FightMapstruct.ME.convert(playerEntity);
room.ofRangeBroadcast(flowContext)
.setResponseMessage(RoomCmd.of(RoomCmd.playerEnterRoomBroadcast), fightPlayer)
// 排除不需要通知的玩家(当前 userId 是自己)
.removeUserId(userId)
.execute();
});
```
---
### 2024-04-16 - v21.5
https://github.com/iohao/ioGame/releases/tag/21.5
1. 增强 ClassScanner 类
2. 优化模拟客户端
3. [#258](about:blank) 文档生成,兼容 gradle 编译路径
4. enhance jprotobuf,临时解决打包后不能在 linux java21 环境运行的问题,see [java21,springBoot3.2 打 jar 后使用异常 · Issue #211 · jhunters/jprotobuf (github.com)](https://github.com/jhunters/jprotobuf/issues/211)
5. 生成 .proto 时,在最后打印文件路径
1. [#255](https://github.com/iohao/ioGame/issues/255) 关于 Proto 生成排除属性问题
```java
/**
* 动物
*/
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class Animal {
/** id */
int id;
/** 动物类型 - 枚举测试 */
AnimalType animalType;
/** 年龄 - 忽略的属性*/
@Ignore
String age;
}
```
生成后的 .proto
```protobuf
// 动物
message Animal {
// id
int32 id = 1;
// 动物类型 - 枚举测试
AnimalType animalType = 2;
}
```
---
### 2024-03-28 - v21.4
https://github.com/iohao/ioGame/releases/tag/21.4
[ #253](https://github.com/iohao/ioGame/issues/253)
> CreateRoomInfo.createUserId int --> long
ExecutorRegion
> 1. 优化默认创建策略
> 2. 优化 ExecutorRegionKit,SimpleThreadExecutorRegion 默认使用全局单例,减少对象的创建。
proto 文档生成时,默认指定为 StandardCharsets.UTF_8
> javaProjectBuilder.setEncoding(StandardCharsets.UTF_8.name());
玩家下线时,使用自身所关联的线程处理。
> SocketUserSessions removeUserSession
```java
public void removeUserSession(SocketUserSession userSession) {
if (Objects.isNull(userSession)) {
return;
}
long userId = userSession.getUserId();
ExecutorRegionKit.getSimpleThreadExecutor(userId)
.executeTry(() -> internalRemoveUserSession(userSession));
}
```
---
### 2024-03-11 - v21.3
https://github.com/game-town/ioGame/releases/tag/21.3
[#250](https://github.com/game-town/ioGame/issues/250) 游戏对外服 - 自定义编解码 - WebSocketMicroBootstrapFlow
重写 WebSocketMicroBootstrapFlow createExternalCodec 方法,用于创建开发者自定义的编解码,其他配置则使用 pipelineCodec 中的默认配置。
```java
DefaultExternalServerBuilder builder = ...;
builder.setting().setMicroBootstrapFlow(new WebSocketMicroBootstrapFlow() {
@Override
protected MessageToMessageCodec createExternalCodec() {
// 开发者自定义的编解码实现类。
return new YourWsExternalCodec();
}
});
```
以下展示的是 WebSocketMicroBootstrapFlow pipelineCodec 相关代码
```java
public class WebSocketMicroBootstrapFlow extends SocketMicroBootstrapFlow {
... 省略部分代码
@Override
public void pipelineCodec(PipelineContext context) {
// 添加 http 相关 handler
this.httpHandler(context);
// 建立连接前的验证 handler
this.verifyHandler(context);
// 添加 websocket 相关 handler
this.websocketHandler(context);
// websocket 编解码
var externalCodec = this.createExternalCodec();
context.addLast("codec", externalCodec);
}
@Override
protected MessageToMessageCodec createExternalCodec() {
// createExternalCodec 相当于一个钩子方法。
return new WebSocketExternalCodec();
}
};
```
[#249](https://github.com/game-town/ioGame/issues/249)
将集群启动顺序放到 Broker(游戏网关)之后。
集群增减和逻辑服 Connect 增减使用同一线程处理。
IoGameGlobalConfig brokerClusterLog 集群相关日志不开启。
---
### 2024-02-22 - v21.2
修复版本号显示错误问题(该版本没有功能上的更新与修改,不升级也不影响)
### 2024-02-21 - v21.1
https://github.com/game-town/ioGame/releases/tag/21.1
ioGame21 首发计划
| 功能支持 | 完成 | 描述 | issu |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----|-----------|--------------------------------------------------------|
| 游戏对外服开放自定义协议 | ✅ | 功能增强 | [#213](https://github.com/game-town/ioGame/issues/213) |
| 游戏对外服缓存 | ✅ | 功能增强、性能提升 | [#76](https://github.com/game-town/ioGame/issues/76) |
| FlowContext 增加通信能力,提供同步、异步、异步回调的便捷使用 | ✅ | 功能增强 | [#235](https://github.com/game-town/ioGame/issues/235) |
| 虚拟线程支持; 各逻辑服之间通信阻塞部分,改为使用虚拟线程,避免阻塞业务线程 | ✅ | 功能增强、性能提升 | |
| 默认不使用 bolt 线程池,减少上下文切换。 ioGame17:netty --> bolt 线程池 --> ioGame 线程池。 ioGame21: 1. netty --> ioGame 线程池。 2. 部分业务将直接在 netty 线程中消费业务。 | ✅ | 性能提升 | |
| 全链路调用日志跟踪;日志增强 traceId | ✅ | 功能增强 | [#230](https://github.com/game-town/ioGame/issues/230) |
| 移除过期代码 | ✅ | 整理 | [#237](https://github.com/game-town/ioGame/issues/239) |
| 分布式事件总线可以代替 redis pub sub 、 MQ ,并且具备全链路调用日志跟踪,这点是中间件产品做不到的。 | ✅ | 功能增强 | [#228](https://github.com/game-town/ioGame/issues/228) |
| 日志库使用新版本 slf4j 2.0 | ✅ | | |
| 心跳响应前的回调 | ✅ | 功能增强 | [#234](https://github.com/game-town/ioGame/issues/234) |
| FlowContext 增加更新、获取元信息的便捷使用 | ✅ | 功能增强 | [#236](https://github.com/game-town/ioGame/issues/236) |
### 2024
#### ioGame21 首发内容简介
在 ioGame21 中,该版本做了数百项优化及史诗级增强。
- 文档方面
- 线程管理域方面的开放与统一、减少线程池上下文切换
- FlowContext 得到了**史诗级**的增强。
- 新增通讯方式 - 分布式事件总线
- 游戏对外服方面增强
- 全链路调用日志跟踪
- 各逻辑服之间通信阻塞部分,改为使用虚拟线程, 避免阻塞业务线程,从而使得框架的吞吐量得到了巨大的提升。
##### 游戏对外服相关
[#76](https://github.com/game-town/ioGame/issues/76) 游戏对外服缓存
游戏对外服缓存,可以将一些热点的业务数据缓存在游戏对外服中,玩家每次访问相关路由时,会直接从游戏对外服的内存中取数据。这样可以避免反复请求游戏逻辑服,从而达到性能的超级提升;
```java
private static void extractedExternalCache() {
// 框架内置的缓存实现类
DefaultExternalCmdCache externalCmdCache = new DefaultExternalCmdCache();
// 添加到配置中
ExternalGlobalConfig.externalCmdCache = externalCmdCache;
// 配置缓存 3-1
externalCmdCache.addCmd(3, 1);
}
```
[#213](https://github.com/game-town/ioGame/issues/213) 游戏对外服开放自定义协议
开发者可自定义游戏对外服协议,用于代替框架默认的 ExternalMessage 公共对外协议。
[#234](https://github.com/game-town/ioGame/issues/234) 心跳响应前的回调
在部分场景下,在响应心跳前可添加当前时间,使得客户端与服务器时间同步。
```java
@Slf4j
public class DemoIdleHook implements SocketIdleHook {
... ... 省略部分代码
volatile byte[] timeBytes;
public DemoIdleHook() {
updateTime();
// 每秒更新当前时间
TaskKit.runInterval(this::updateTime, 1, TimeUnit.SECONDS);
}
private void updateTime() {
LongValue data = LongValue.of(TimeKit.currentTimeMillis());
// 避免重复序列化,这里提前序列化好时间数据
timeBytes = DataCodecKit.encode(data);
}
@Override
public void pongBefore(BarMessage idleMessage) {
// 把当前时间戳给到心跳接收端
idleMessage.setData(timeBytes);
}
}
```
##### FlowContext - 跨服通信
[#235](https://github.com/game-town/ioGame/issues/235) FlowContext 增加通信能力,提供同步、异步、异步回调的便捷使用
```java
// 跨服请求 - 同步、异步回调演示
void invokeModuleMessage() {
// 路由、请求参数
ResponseMessage responseMessage = flowContext.invokeModuleMessage(cmdInfo, yourData);
RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
log.info("同步调用 : {}", roomNumMsg.roomCount);
// --- 此回调写法,具备全链路调用日志跟踪 ---
// 路由、请求参数、回调
flowContext.invokeModuleMessageAsync(cmdInfo, yourData, responseMessage -> {
RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
log.info("异步回调 : {}", roomNumMsg.roomCount);
});
}
// 广播
public void broadcast(FlowContext flowContext) {
// 全服广播 - 路由、业务数据
flowContext.broadcast(cmdInfo, yourData);
// 广播消息给单个用户 - 路由、业务数据、userId
long userId = 100;
flowContext.broadcast(cmdInfo, yourData, userId);
// 广播消息给指定用户列表 - 路由、业务数据、userIdList
List userIdList = new ArrayList<>();
userIdList.add(100L);
userIdList.add(200L);
flowContext.broadcast(cmdInfo, yourData, userIdList);
// 给自己发送消息 - 路由、业务数据
flowContext.broadcastMe(cmdInfo, yourData);
// 给自己发送消息 - 业务数据
// 路由则使用当前 action 的路由。
flowContext.broadcastMe(yourData);
}
```
[#236](https://github.com/game-town/ioGame/issues/236) FlowContext 增加更新、获取元信息的便捷使用
```java
void test(MyFlowContext flowContext) {
// 获取元信息
MyAttachment attachment = flowContext.getAttachment();
attachment.nickname = "渔民小镇";
// [同步]更新 - 将元信息同步到玩家所在的游戏对外服中
flowContext.updateAttachment();
// [异步无阻塞]更新 - 将元信息同步到玩家所在的游戏对外服中
flowContext.updateAttachmentAsync();
}
public class MyFlowContext extends FlowContext {
MyAttachment attachment;
@Override
@SuppressWarnings("unchecked")
public MyAttachment getAttachment() {
if (Objects.isNull(attachment)) {
this.attachment = this.getAttachment(MyAttachment.class);
}
return this.attachment;
}
}
```
##### 线程相关 - 无锁高并发
虚拟线程支持,各逻辑服之间通信阻塞部分使用虚拟线程来处理,避免阻塞业务线程。
默认不使用 bolt 线程池,减少上下文切换。ioGame21 业务消费的线程相关内容如下:
1. netty --> ioGame 线程池。
2. 部分业务将直接在 netty 线程中消费业务。
在 ioGame21 中,框架内置了 3 个线程执行器管理域,分别是
1. UserThreadExecutorRegion ,用户线程执行器管理域。
2. UserVirtualThreadExecutorRegion ,用户虚拟线程执行器管理域。
3. SimpleThreadExecutorRegion ,简单的线程执行器管理域。
**从工具类中得到与用户(玩家)所关联的线程执行器**
```java
@Test
public void userThreadExecutor() {
long userId = 1;
ThreadExecutor userThreadExecutor = ExecutorRegionKit.getUserThreadExecutor(userId);
userThreadExecutor.execute(() -> {
// print 1
log.info("userThreadExecutor : 1");
});
userThreadExecutor.execute(() -> {
// print 2
log.info("userThreadExecutor : 2");
});
}
@Test
public void getUserVirtualThreadExecutor() {
long userId = 1;
ThreadExecutor userVirtualThreadExecutor = ExecutorRegionKit.getUserVirtualThreadExecutor(userId);
userVirtualThreadExecutor.execute(() -> {
// print 1
log.info("userVirtualThreadExecutor : 1");
});
userVirtualThreadExecutor.execute(() -> {
// print 2
log.info("userVirtualThreadExecutor : 2");
});
}
@Test
public void getSimpleThreadExecutor() {
long userId = 1;
ThreadExecutor simpleThreadExecutor = ExecutorRegionKit.getSimpleThreadExecutor(userId);
simpleThreadExecutor.execute(() -> {
// print 1
log.info("simpleThreadExecutor : 1");
});
simpleThreadExecutor.execute(() -> {
// print 2
log.info("simpleThreadExecutor : 2");
});
}
```
**从 FlowContext 中得到与用户(玩家)所关联的线程执行器**
```java
void executor() {
// 该方法具备全链路调用日志跟踪
flowContext.execute(() -> {
log.info("用户线程执行器");
});
// 正常提交任务到用户线程执行器中
// getExecutor() 用户线程执行器
flowContext.getExecutor().execute(() -> {
log.info("用户线程执行器");
});
}
void executeVirtual() {
// 该方法具备全链路调用日志跟踪
flowContext.executeVirtual(() -> {
log.info("用户虚拟线程执行器");
});
// 正常提交任务到用户虚拟线程执行器中
// getVirtualExecutor() 用户虚拟线程执行器
flowContext.getVirtualExecutor().execute(() -> {
log.info("用户虚拟线程执行器");
});
// 示例演示 - 更新元信息(可以使用虚拟线程执行完成一些耗时的操作)
flowContext.executeVirtual(() -> {
log.info("用户虚拟线程执行器");
// 更新元信息
flowContext.updateAttachment();
// ... ... 其他业务逻辑
});
}
```
##### 日志相关
日志库使用新版本 slf4j 2.x
[#230](https://github.com/game-town/ioGame/issues/230) 支持全链路调用日志跟踪;
**开启 traceId 特性**
该配置需要在游戏对外服中设置,因为游戏对外服是玩家请求的入口。
```java
// true 表示开启 traceId 特性
IoGameGlobalConfig.openTraceId = true;
```
将全链路调用日志跟踪插件 TraceIdInOut 添加到业务框架中,表示该游戏逻辑服需要支持全链路调用日志跟踪。如果游戏逻辑服没有添加该插件的,表示不需要记录日志跟踪。
```java
BarSkeletonBuilder builder = ...;
// traceId
TraceIdInOut traceIdInOut = new TraceIdInOut();
builder.addInOut(traceIdInOut);
```
##### 分布式事件总线 - 跨服解耦
[#228](https://github.com/game-town/ioGame/issues/228) 分布式事件总线是新增的通讯方式,可以代替 redis pub sub 、 MQ ...等中间件产品;分布式事件总线具备全链路调用日志跟踪,这点是中间件产品所做不到的。
**ioGame 分布式事件总线,特点**
- 使用方式与 Guava EventBus 类似
- 具备**全链路调用日志跟踪**。(这点是中间件产品做不到的)
- 支持跨多个机器、多个进程通信
- 支持与多种不同类型的多个逻辑服通信
- 纯 javaSE,不依赖其他服务,耦合性低。(不需要安装任何中间件)
- 事件源和事件监听器之间通过事件进行通信,从而实现了模块之间的解耦
- 当没有任何远程订阅者时,**将不会触发网络请求**。(这点是中间件产品做不到的)
下面两个订阅者是分别在**不同的进程中**的,当事件发布后,这两个订阅者都能接收到 UserLoginEventMessage 消息。
```java
@ActionController(UserCmd.cmd)
public class UserAction {
... 省略部分代码
@ActionMethod(UserCmd.fireEvent)
public String fireEventUser(FlowContext flowContext) {
long userId = flowContext.getUserId();
log.info("fire : {} ", userId);
// 事件源
var userLoginEventMessage = new UserLoginEventMessage(userId);
// 发布事件
flowContext.fire(userLoginEventMessage);
return "fireEventUser";
}
}
// 该订阅者在 【UserLogicStartup 逻辑服】进程中,与 UserAction 同在一个进程
@EventBusSubscriber
public class UserEventBusSubscriber {
@EventSubscribe(ExecutorSelector.userExecutor)
public void userLogin(UserLoginEventMessage message) {
log.info("event - 玩家[{}]登录,记录登录时间", message.getUserId());
}
}
// 该订阅者在 【EmailLogicStartup 逻辑服】进程中。
@EventBusSubscriber
public class EmailEventBusSubscriber {
@EventSubscribe
public void mail(UserLoginEventMessage message) {
long userId = message.getUserId();
log.info("event - 玩家[{}]登录,发放 email 奖励", userId);
}
}
```
##### 小结
在 ioGame21 中,该版本做了数百项优化及史诗级增强。
- 在线文档方面
- 线程管理域方面的开放与统一、减少线程池上下文切换
- FlowContext 增强
- 新增通讯方式 - 分布式事件总线
- 游戏对外服方面增强
- 全链路调用日志跟踪
#### ioGame17 迁移到 ioGame21
文档:[17 迁移到 ioGame21](https://www.yuque.com/iohao/game/hcgsfobyoph9r74r)
#### ioGame17 - 更新日志
see online [ioGame17 - 更新日志](https://iohao.github.io/game/docs/archive/version_log_17)
================================================
FILE: common/README.md
================================================
```text
.
├── common-core 业务框架
└── common-kit 工具相关
```
================================================
FILE: common/common-core/README.md
================================================
## 业务框架
如果说 [sofa-bolt](https://www.sofastack.tech/projects/sofa-bolt/overview/) 是为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上。而业务框架正是解决业务逻辑如何方便实现这一问题上。业务框架是游戏框架的一部分,职责是简化程序员的业务逻辑实现,业务框架使程序员能够快速的开始编写游戏业务。
业务框架对于每个 action (即业务的处理类) 都是通过 [asm](https://www.oschina.net/p/reflectasm) 与 Singleton、Flyweight 、Command 等设计模式结合,对 action 的获取上通过 array 来得到,是一种近原生的方式。
业务框架平均每秒可以执行 1152 万次业务逻辑。
================================================
FILE: common/common-core/pom.xml
================================================
ioGamecom.iohao.game21.34../../pom.xml4.0.0common-corecommon-core for ioGamecom.iohao.gamecommon-kit${project.parent.version}com.iohao.gamecommon-validation${project.parent.version}com.alibaba.fastjson2fastjson2${fastjson.version}providedorg.fusesource.jansijansi${jansi.version}com.thoughtworks.qdoxqdox${qdox.version}jakarta.validationjakarta.validation-api${jakarta.validation-api.version}providedorg.springframeworkspring-context${spring.version}providedcom.iohao.gamelight-jprotobuf${project.parent.version}testorg.hibernate.validatorhibernate-validator${hibernate-validator.version}testorg.glassfishjakarta.el${jakarta.el.version}test
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/IoGameVersion.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton;
/**
* @author 渔民小镇
* @date 2022-12-23
*/
public final class IoGameVersion {
public static final String VERSION;
static {
String internalVersion = "21.34";
VERSION = internalVersion
.replace("", "")
.replace("", "")
;
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/ActionController.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.annotation;
import java.lang.annotation.*;
/**
* 控制器注解, 一般用作类的路由
*
* 类 cmd 注解
* 一般用作类的路由
*
*
* @author 渔民小镇
* @date 2021-12-12
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActionController {
/**
* @return 路由
*/
int value();
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/ActionMethod.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.annotation;
import java.lang.annotation.*;
/**
* 方法注解, 一般用作类方法的路由
*
* 方法 subCmd 注解
* 一般用作类方法的路由
*
*
* @author 渔民小镇
* @date 2021-12-12
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActionMethod {
/**
* subCmd int
*
* @return subCmd
*/
int value();
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/DocActionSend.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.annotation;
import com.iohao.game.action.skeleton.core.doc.BroadcastDocument;
import java.lang.annotation.*;
/**
* 文档相关
*
* 仅用于推送文档生成
*
*
* @author 渔民小镇
* @date 2022-01-31
* @deprecated 请使用 {@link BroadcastDocument} 代替
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(DocActionSends.class)
@Deprecated
public @interface DocActionSend {
/** 主路由 */
int cmd();
/** 子路由 */
int subCmd();
/** 业务对象class */
Class> dataClass();
/** 推送描述 */
String description() default "";
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/DocActionSends.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.annotation;
import com.iohao.game.action.skeleton.core.doc.BroadcastDocument;
import java.lang.annotation.*;
/**
* 文档相关
*
* 仅用于推送文档生成
*
*
* @author 渔民小镇
* @date 2022-01-31
* @deprecated 请使用 {@link BroadcastDocument} 代替
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Deprecated
public @interface DocActionSends {
DocActionSend[] value();
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/ValidatedGroup.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.annotation;
import java.lang.annotation.*;
/**
* 验证组
*
* @author fangwei
* @date 2022-09-20
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
public @interface ValidatedGroup {
/**
* 确定验证组,校验组对象的 Class
*
* @return 校验组对象的 Class 数组
*/
Class>[] value() default {};
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/annotation/package-info.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
/**
* 业务框架 - 注解相关
*
* @author 渔民小镇
* @date 2024-08-05
*/
package com.iohao.game.action.skeleton.annotation;
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommand.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.iohao.game.action.skeleton.annotation.ValidatedGroup;
import com.iohao.game.action.skeleton.core.doc.ActionCommandDoc;
import com.iohao.game.action.skeleton.core.flow.FlowContext;
import com.iohao.game.action.skeleton.core.flow.parser.MethodParser;
import com.iohao.game.action.skeleton.core.flow.parser.MethodParsers;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
/**
* ActionCommand 命令对象,也称为 action。
*
*/
final Class> actualClazz;
final boolean customMethodParser;
private ActionMethodReturnInfo(ActionCommand.Builder builder) {
this.returnTypeClazz = builder.returnTypeClazz;
if (List.class.isAssignableFrom(returnTypeClazz)) {
ParameterizedType genericReturnType = (ParameterizedType) builder.actionMethod.getGenericReturnType();
this.actualTypeArgumentClazz = (Class>) genericReturnType.getActualTypeArguments()[0];
this.list = true;
} else {
this.actualTypeArgumentClazz = returnTypeClazz;
this.list = false;
}
MethodParser methodParser = MethodParsers.getMethodParser(this);
this.actualClazz = methodParser.getActualClazz(this);
this.customMethodParser = methodParser.isCustomMethodParser();
}
/**
* 方法返回值类型是否 void
*
* @return true 是 void
*/
public boolean isVoid() {
return Void.TYPE == this.returnTypeClazz;
}
@Override
public String toString() {
return toString(false);
}
public String toString(boolean fullName) {
boolean isCustomList = this.list && !MethodParsers.containsKey(this.actualClazz);
if (isCustomList) {
String simpleNameReturnTypeClazz = this.returnTypeClazz.getSimpleName();
String simpleNameActualClazz = fullName
? this.actualClazz.getName()
: this.actualClazz.getSimpleName();
return String.format("%s<%s>", simpleNameReturnTypeClazz, simpleNameActualClazz);
}
return fullName
? this.actualClazz.getName()
: this.actualClazz.getSimpleName();
}
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandDocParser.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.annotation.ActionController;
import com.iohao.game.action.skeleton.annotation.ActionMethod;
import com.iohao.game.action.skeleton.core.doc.*;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import org.jctools.maps.NonBlockingHashMap;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* source doc 解析类
*
* @author 渔民小镇
* @date 2022-12-09
*/
@FieldDefaults(level = AccessLevel.PRIVATE)
final class ActionCommandDocParser {
final ActionCommandParser actionCommandParser;
final Map actionCommandDocMap = new NonBlockingHashMap<>();
final ActionCommandDoc emptyActionCommandDoc = new ActionCommandDoc();
ActionCommandDocParser(ActionCommandParser actionCommandParser, List> controllerList, boolean parseDoc) {
this.actionCommandParser = actionCommandParser;
if (parseDoc) {
this.buildSourceDoc(controllerList);
}
}
private void buildSourceDoc(List> controllerList) {
var actionCommandRegions = this.actionCommandParser.getActionCommandRegions();
// java source
Map javaClassDocInfoMap = ActionCommandDocKit.getJavaClassDocInfoMap(controllerList);
this.actionCommandParser.getActionControllerStream(controllerList).parallel().forEach(controllerClazz -> {
// java source
JavaClassDocInfo javaClassDocInfo = javaClassDocInfoMap.get(controllerClazz.toString());
// 主路由 (类上的路由)
int cmd = controllerClazz.getAnnotation(ActionController.class).value();
// 过期的方法,将来需要删除的部分
extractedDeprecate(actionCommandRegions, controllerClazz, javaClassDocInfo, cmd);
// action 文档
ActionDoc actionDoc = IoGameDocumentHelper.ofActionDoc(cmd, controllerClazz);
actionDoc.setJavaClassDocInfo(javaClassDocInfo);
this.actionCommandParser.getMethodStream(controllerClazz).forEach(method -> {
ActionCommandDoc actionCommandDoc = getActionCommandDoc(javaClassDocInfo, method);
// 目标子路由 (方法上的路由)
int subCmd = method.getAnnotation(ActionMethod.class).value();
int cmdMerge = CmdKit.merge(cmd, subCmd);
// 将数据保存在这里。
actionCommandDocMap.put(cmdMerge, actionCommandDoc);
actionDoc.addActionCommandDoc(actionCommandDoc);
});
});
}
private static void extractedDeprecate(ActionCommandRegions actionCommandRegions, Class> controllerClazz, JavaClassDocInfo javaClassDocInfo, int cmd) {
var actionCommandRegion = actionCommandRegions.getActionCommandRegion(cmd);
actionCommandRegion.setActionControllerClazz(controllerClazz);
actionCommandRegion.setJavaClassDocInfo(javaClassDocInfo);
}
ActionCommandDoc getActionCommandDoc(int cmd, int subCmd) {
int cmdMerge = CmdKit.merge(cmd, subCmd);
return actionCommandDocMap.getOrDefault(cmdMerge, emptyActionCommandDoc);
}
private ActionCommandDoc getActionCommandDoc(JavaClassDocInfo javaClassDocInfo, Method method) {
if (javaClassDocInfo != null) {
return javaClassDocInfo.createActionCommandDoc(method);
}
return new ActionCommandDoc();
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandFlowExecute.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.core.flow.FlowContext;
/**
* 命令流程执行器
*
* @author 渔民小镇
* @date 2021-12-20
*/
public interface ActionCommandFlowExecute {
/**
* 模板方法模式:
*
*
* @param flowContext FlowContext
*/
void execute(FlowContext flowContext);
static ActionCommandFlowExecute defaultInstance() {
return DefaultActionCommandFlowExecute.me();
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandHandler.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.core.flow.FlowContext;
import com.iohao.game.action.skeleton.core.flow.FlowContextKit;
import lombok.extern.slf4j.Slf4j;
/**
* 该handler用于执行 {@link ActionCommand} 对象
*
* @author 渔民小镇
* @date 2021-12-12
*/
@Slf4j
class ActionCommandHandler implements Handler {
@Override
public boolean handler(final FlowContext flowContext) {
try {
// 设置 flowContext 的一些属性
this.settingFlowContext(flowContext);
// actionCommand 命令流程执行器
ActionCommandFlowExecute.defaultInstance().execute(flowContext);
} catch (Throwable e) {
log.error(e.getMessage(), e);
return false;
}
return true;
}
protected void settingFlowContext(FlowContext flowContext) {
/*
* 做一些兼容,这部分逻辑在 RequestMessageClientProcessor 已经设置过一次,
* 这里做兼容的目的有两个:
* 1 防止开发者将 RequestMessageClientProcessor 移除,并做了相关的自定义。
* 2 业务框架可以单独做测试
*/
FlowContextKit.employ(flowContext);
// 业务框架
BarSkeleton barSkeleton = flowContext.getBarSkeleton();
// 参数解析器
var paramParser = barSkeleton.getActionMethodParamParser();
// 得到业务方法的参数列表,并验证
var params = paramParser.listParam(flowContext);
// 业务方法参数 save to flowContext
flowContext.setMethodParams(params);
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandParser.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.iohao.game.action.skeleton.annotation.ActionController;
import com.iohao.game.action.skeleton.annotation.ActionMethod;
import com.iohao.game.action.skeleton.core.action.parser.ActionParserContext;
import com.iohao.game.action.skeleton.core.action.parser.ActionParserListener;
import com.iohao.game.action.skeleton.core.codec.ProtoDataCodec;
import com.iohao.game.action.skeleton.core.doc.ActionCommandDoc;
import com.iohao.game.action.skeleton.core.doc.ActionDoc;
import com.iohao.game.action.skeleton.core.doc.IoGameDocumentHelper;
import com.iohao.game.action.skeleton.toy.IoGameBanner;
import com.iohao.game.common.kit.StrKit;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import org.jctools.maps.NonBlockingHashMap;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Stream;
/**
* action 命令对象解析器,将 action 类下的业务方法解析为 actionCommand
*
*
* @param actionControllerClass 类
* @return 标准 action 方法对象 Stream
*/
Stream getMethodStream(Class> actionControllerClass) {
return Arrays
// 得到 action 类的所有方法
.stream(actionControllerClass.getDeclaredMethods())
// 得到在业务方法上添加了 ActionMethod 注解的方法对象
.filter(method -> Objects.nonNull(method.getAnnotation(ActionMethod.class)))
// 访问权限必须是 public 的
.filter(method -> Modifier.isPublic(method.getModifiers()))
// 不能是静态方法的
.filter(method -> !Modifier.isStatic(method.getModifiers()));
}
private boolean deliveryContainer(Class> controllerClazz) {
if (DependencyInjectionPart.me().isInjection()) {
return DependencyInjectionPart.me().deliveryContainer(controllerClazz);
}
return false;
}
private void paramInfo(Method method, ActionCommand.Builder builder) {
// 方法参数列表
var parameters = method.getParameters();
if (Objects.isNull(parameters)) {
return;
}
var paramInfos = new ActionCommand.ParamInfo[parameters.length];
builder.setParamInfos(paramInfos);
for (int i = 0; i < parameters.length; i++) {
// 方法的参数对象
Parameter parameter = parameters[i];
// 构建参数信息
var paramInfo = new ActionCommand.ParamInfo(i, parameter);
paramInfos[i] = paramInfo;
/*
* 下面是 JSR380 相关的逻辑
*
* 1 没开启 JSR380 验证, 不做处理
* 2 过滤不需要验证的参数
*/
if (!this.setting.validator || paramInfo.isFlowContext()) {
continue;
}
paramInfo.validator = ValidatorKit.isValidator(parameter.getType());
}
}
private void checkExistSubCmd(Class> controllerClass, int subCmd, ActionCommandRegion actionCommandRegion) {
if (actionCommandRegion.containsKey(subCmd)) {
String message = StrKit.format("cmd:【{}】下已经存在方法编号 subCmd:【{}】 .请查看: {}",
actionCommandRegion.cmd,
subCmd,
controllerClass);
IoGameBanner.me().ofRuntimeException(message);
}
}
private void checkSoleActionName(Method method,
ActionCommandRegion actionCommandRegion,
ActionCommandDocParser doc) {
for (ActionCommand command : actionCommandRegion.values()) {
if (Objects.equals(method.getName(), command.actionMethodName)) {
int cmd = actionCommandRegion.cmd;
// 目标子路由 (方法上的路由)
int subCmd = method.getAnnotation(ActionMethod.class).value();
ActionCommandDoc actionCommandDoc = doc.getActionCommandDoc(cmd, subCmd);
String template = """
[action 方法重名] 同一个类中相同的 action 方法名只允许存在一个,建议与路由同名。
see-1 : %s - %s.%s(%s.java:%d)
see-2 : %s - %s.%s(%s.java:%d)""";
Class> actionControllerClazz = actionCommandRegion.getActionControllerClazz();
String simpleName = actionControllerClazz.getSimpleName();
var message = template.formatted(
CmdInfo.of(cmd, subCmd), actionControllerClazz, method.getName()
, simpleName, actionCommandDoc.getLineNumber(),
command.getCmdInfo(), actionControllerClazz, method.getName()
, simpleName, command.getActionCommandDoc().getLineNumber()
);
IoGameBanner.me().ofRuntimeException(message);
}
}
}
private Object ofActionInstance(Class> controllerClazz) {
// 如果 actionController 交给容器管理了,就从容器中获取实例,否则就 newInstance
boolean deliveryContainer = this.deliveryContainer(controllerClazz);
var actionInstance = deliveryContainer
? DependencyInjectionPart.me().getBean(controllerClazz)
: ConstructorAccess.get(controllerClazz).newInstance();
// 正常来说不会为 null,除非是开发者在集成其他容器时,没有实现 getBean 方法
Objects.requireNonNull(actionInstance);
return actionInstance;
}
private void executeActionListeners() {
actionCommandRegions.regionMap.forEach((cmd, actionCommandRegion) -> {
// action command, actionMethod
actionCommandRegion.getSubActionCommandMap().forEach((subCmd, command) -> {
// action 构建时的上下文
var context = new ActionParserContext()
.setBarSkeleton(barSkeleton)
.setActionCommand(command);
// action 构建时的监听 - actionCommand
this.actionParserListeners.onActionCommand(context);
});
});
this.actionParserListeners.onAfter(barSkeleton);
}
}
@FieldDefaults(level = AccessLevel.PRIVATE)
final class ActionParserListeners {
final Map, ActionParserListener> map = new NonBlockingHashMap<>();
void addActionParserListener(ActionParserListener listener) {
Objects.requireNonNull(listener);
this.map.putIfAbsent(listener.getClass(), listener);
}
void onActionCommand(ActionParserContext context) {
this.map.forEach((clazz, listener) -> listener.onActionCommand(context));
}
void onAfter(BarSkeleton barSkeleton) {
this.map.forEach((clazz, listener) -> listener.onAfter(barSkeleton));
}
ActionParserListeners() {
// 监听器 - 预先创建协议代理类
if (DataCodecKit.getDataCodec() instanceof ProtoDataCodec) {
this.addActionParserListener(new ProtobufActionParserListener());
this.addActionParserListener(new ProtobufCheckActionParserListener());
}
// prepared actionControllerConstructorAccess
this.addActionParserListener(context -> {
var actionCommand = context.getActionCommand();
if (!actionCommand.isDeliveryContainer()) {
actionCommand.actionControllerConstructorAccess = ConstructorAccess.get(actionCommand.actionControllerClazz);
}
});
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandRegion.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.core.doc.JavaClassDocInfo;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
import org.jctools.maps.NonBlockingHashMap;
import java.util.Collection;
import java.util.Map;
/**
* ActionCommand 域,通常与 ActionController 是 1:1 的关系
*
*
* 类似模块的区分,这样可以避免 map 嵌 map 的结构
* 在代码的阅读上也会清晰很多
*
*
* @author 渔民小镇
* @date 2022-05-15
*/
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PACKAGE)
public final class ActionCommandRegion {
final int cmd;
/** actionControllerClazz */
Class> actionControllerClazz;
/** actionControllerClazz 的源文件信息 */
JavaClassDocInfo javaClassDocInfo;
/**
*
* key: subCmd
* value: ActionCommand
*
*/
Map subActionCommandMap = new NonBlockingHashMap<>();
public ActionCommandRegion(int cmd) {
this.cmd = cmd;
}
public boolean containsKey(int subCmd) {
return this.subActionCommandMap.containsKey(subCmd);
}
public void add(ActionCommand subActionCommand) {
var cmdInfo = subActionCommand.getCmdInfo();
int subCmd = cmdInfo.getSubCmd();
this.subActionCommandMap.put(subCmd, subActionCommand);
}
/**
* 得到子路由最大值
*
* @return 子路由最大值
*/
public int getMaxSubCmd() {
return subActionCommandMap
.keySet()
.stream()
.max(Integer::compareTo)
.orElse(0)
;
}
public Collection values() {
return this.subActionCommandMap.values();
}
/**
* 将子路由列表转为数组
*
* @return array
*/
public ActionCommand[] arrayActionCommand() {
// 子路由最大值
int subCmdMax = this.getMaxSubCmd() + 1;
ActionCommand[] subBehaviors = new ActionCommand[subCmdMax];
for (Map.Entry subEntry : subActionCommandMap.entrySet()) {
subBehaviors[subEntry.getKey()] = subEntry.getValue();
}
return subBehaviors;
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandRegionGlobalCheckKit.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.common.kit.StrKit;
import com.iohao.game.common.kit.exception.ThrowKit;
import lombok.experimental.UtilityClass;
import java.util.HashMap;
import java.util.Map;
/**
* 全局重复路由检测工具
*
*
* @author 渔民小镇
* @date 2022-07-31
*/
@UtilityClass
public class ActionCommandRegionGlobalCheckKit {
Map map = new HashMap<>();
public void putActionCommandRegions(String key, ActionCommandRegions actionCommandRegions) {
if (map.containsKey(key)) {
return;
}
map.put(key, actionCommandRegions);
}
/**
* 全局重复路由检测
*/
public void checkGlobalExistSubCmd() {
Map cmdMap = new HashMap<>(100);
// 多服单进程下的所有业务框架的命令域管理器
var actionCommandRegionList = map
.values()
.parallelStream()
.flatMap(ActionCommandRegions::streamActionCommandRegion)
.toList();
for (ActionCommandRegion actionCommandRegion : actionCommandRegionList) {
// 命令域下的路由 action
for (ActionCommand actionCommand : actionCommandRegion.values()) {
// 路由信息
CmdInfo cmdInfo = actionCommand.getCmdInfo();
int cmdMerge = cmdInfo.getCmdMerge();
// 如果是重复路由,就抛异常
if (cmdMap.containsKey(cmdMerge)) {
String template = """
全局重复路由检测,使用了相同的路由,或者多个业务框架中,加载了相同的 action
cmd:【{}】下已经存在方法编号 subCmd:【{}】 .请查看: {}
""";
String message = StrKit.format(template,
actionCommandRegion.cmd,
cmdInfo.getSubCmd(),
actionCommand.getActionControllerClazz()
);
ThrowKit.ofRuntimeException(message);
}
cmdMap.put(cmdMerge, actionCommand);
}
}
// 清空数据,检测完了
map.clear();
cmdMap.clear();
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandRegions.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.i18n.Bundle;
import com.iohao.game.action.skeleton.i18n.MessageKey;
import com.iohao.game.action.skeleton.toy.IoGameBanner;
import com.iohao.game.common.kit.MoreKit;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import org.jctools.maps.NonBlockingHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 命令域 管理器
*
* 管理命令域
*
* 路由与子路由的关系维护
*
*
* @author 渔民小镇
* @date 2022-05-15
*/
@Getter
@FieldDefaults(level = AccessLevel.PACKAGE)
public final class ActionCommandRegions {
private static final ActionCommand[][] EMPTY = new ActionCommand[0][0];
/**
* action map
*
* key : cmd
* value : subCmd region
*
*/
final Map regionMap = new NonBlockingHashMap<>();
/**
* action 数组. 下标对应 cmd
*
*
* @param barSkeletonSetting config
* @return 二维数组
*/
private ActionCommand[][] convertArray(BarSkeletonSetting barSkeletonSetting) {
if (this.regionMap.isEmpty()) {
return EMPTY;
}
// 获取主路由最大值
int max = getMaxCmd(barSkeletonSetting);
var behaviors = new ActionCommand[max][1];
this.regionMap.keySet().forEach(cmd -> {
var actionCommandRegion = this.regionMap.get(cmd);
behaviors[cmd] = actionCommandRegion.arrayActionCommand();
});
return behaviors;
}
private int getMaxCmd(BarSkeletonSetting barSkeletonSetting) {
// 获取最大的路由数字 并且+1
int max = this.regionMap
.keySet()
.stream()
.max(Integer::compareTo)
.orElse(0) + 1;
if (max > barSkeletonSetting.getCmdMaxLen()) {
/*
* %s exceeds the maximum default value.
* Please set the capacity manually if necessary.
* Default maximum capacity %d, current capacity %d
*/
var info = Bundle.getMessage(MessageKey.cmdMergeLimit).formatted(
"cmd", barSkeletonSetting.getCmdMaxLen(), max
);
IoGameBanner.me().ofRuntimeException(info);
}
// subCmd
for (ActionCommandRegion actionCommandRegion : this.regionMap.values()) {
int subCmdMax = actionCommandRegion.getMaxSubCmd() + 1;
if (subCmdMax > barSkeletonSetting.getSubCmdMaxLen()) {
var info = Bundle.getMessage(MessageKey.cmdMergeLimit).formatted(
"subCmd", barSkeletonSetting.getSubCmdMaxLen(), subCmdMax
);
IoGameBanner.me().ofRuntimeException(info);
}
}
return max;
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionFactoryBean.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.annotation.ActionController;
/**
* action 类对象创建工厂,负责创建 action 类的实例化对象
*
* @param t
* @author 渔民小镇
* @date 2021-12-20
*/
public interface ActionFactoryBean {
/**
* 获取 action 类的对象
*
*
* @param actionCommand actionCommand
* @return action 类的实例化对象
*/
T getBean(ActionCommand actionCommand);
/**
* 通过 action class 得到对应 bean
*
* @param actionControllerClazz action class
* @return action 类的实例化对象
*/
default T getBean(Class> actionControllerClazz) {
return null;
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionParserListenerAbout.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
import com.iohao.game.action.skeleton.core.action.parser.ActionParserContext;
import com.iohao.game.action.skeleton.core.action.parser.ActionParserListener;
import com.iohao.game.action.skeleton.i18n.Bundle;
import com.iohao.game.action.skeleton.i18n.MessageKey;
import com.iohao.game.action.skeleton.protocol.wrapper.*;
import com.iohao.game.common.kit.ProtoKit;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
import org.jctools.maps.NonBlockingHashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
/**
* Prepared action proto
*
* @author 渔民小镇
* @date 2024-05-01
* @since 21.7
*/
@FieldDefaults(level = AccessLevel.PRIVATE)
final class ProtobufActionParserListener implements ActionParserListener {
static final Set> protoSet = new NonBlockingHashSet<>();
static {
// create a protobuf proxy class
ProtoKit.create(ByteValueList.class);
ProtoKit.create(IntValue.class);
ProtoKit.create(IntValueList.class);
ProtoKit.create(BoolValue.class);
ProtoKit.create(BoolValueList.class);
ProtoKit.create(LongValue.class);
ProtoKit.create(LongValueList.class);
ProtoKit.create(StringValue.class);
ProtoKit.create(StringValueList.class);
}
@Override
public void onActionCommand(ActionParserContext context) {
// 添加了 ProtobufClass 注解的类
Predicate> protobufClassPredicate = c -> Objects.nonNull(c.getAnnotation(ProtobufClass.class));
collect(context, protobufClassPredicate, protoSet);
}
static void collect(ActionParserContext context, Predicate> protobufClassPredicate, Set> protoSet) {
// 将 action 的方法参数与返回值添加了 ProtobufClass 注解的类信息收集到 protoSet 中
ActionCommand actionCommand = context.getActionCommand();
// action 参数相关
actionCommand.streamParamInfo()
// 只处理业务参数
.filter(ActionCommand.ParamInfo::isBizData)
// 得到参数类型
.map(ActionCommand.ParamInfo::getActualTypeArgumentClazz)
// 协议碎片类型不做处理
.filter(clazz -> !WrapperKit.isWrapper(clazz))
// 添加了 ProtobufClass 注解的类
.filter(protobufClassPredicate)
.forEach(protoSet::add);
// action 返回值相关
Optional
.ofNullable(actionCommand.getActionMethodReturnInfo())
// void 不处理
.filter(actionMethodReturnInfo -> !actionMethodReturnInfo.isVoid())
.map(ActionCommand.ActionMethodReturnInfo::getActualTypeArgumentClazz)
// 协议碎片类型不做处理
.filter(clazz -> !WrapperKit.isWrapper(clazz))
// 添加了 ProtobufClass 注解的类
.filter(protobufClassPredicate)
.ifPresent(protoSet::add);
}
@Override
public void onAfter(BarSkeleton barSkeleton) {
protoSet.forEach(ProtoKit::create);
}
}
/**
* proto 协议类型添检测
*
* @author 渔民小镇
* @date 2024-05-02
* @since 21.7
*/
@Slf4j
final class ProtobufCheckActionParserListener implements ActionParserListener {
static final Set> protoSet = new NonBlockingHashSet<>();
@Override
public void onActionCommand(ActionParserContext context) {
// 添加了 ProtobufClass 注解的类
Predicate> protobufClassPredicate = c -> c.getAnnotation(ProtobufClass.class) == null;
ProtobufActionParserListener.collect(context, protobufClassPredicate, protoSet);
}
@Override
public void onAfter(BarSkeleton barSkeleton) {
if (protoSet.isEmpty()) {
return;
}
log.error(Bundle.getMessage(MessageKey.protobufAnnotationCheck));
for (Class> protoClass : protoSet) {
log.error(protoClass.toString());
}
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionSend.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
/**
* @author 渔民小镇
* @date 2022-01-30
*/
@Deprecated
public interface ActionSend {
/**
* 合并路由
*
* @return 合并路由
* @see CmdKit#merge(int, int)
*/
int getCmdMerge();
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarMessageKit.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.protocol.HeadMetadata;
import com.iohao.game.action.skeleton.protocol.RequestMessage;
import com.iohao.game.action.skeleton.protocol.ResponseMessage;
import lombok.experimental.UtilityClass;
import java.util.Objects;
/**
* 创建 RequestMessage,ResponseMessage 相关内部消息的工具类
*
* @author 渔民小镇
* @date 2022-06-07
* @see RequestMessage
* @see ResponseMessage
*/
@UtilityClass
public class BarMessageKit {
public RequestMessage createRequestMessage(CmdInfo cmdInfo) {
return createRequestMessage(cmdInfo, null);
}
/**
* 创建 RequestMessage
*
* @param cmdInfo 路由
* @param data 业务数据
* @return RequestMessage
*/
public RequestMessage createRequestMessage(CmdInfo cmdInfo, Object data) {
RequestMessage requestMessage = new RequestMessage();
employ(requestMessage, cmdInfo, data);
return requestMessage;
}
/**
* 将路由、业务数据设置到 RequestMessage 中
*
* @param requestMessage RequestMessage
* @param cmdInfo 路由
* @param data 业务数据
*/
public void employ(RequestMessage requestMessage, CmdInfo cmdInfo, Object data) {
var headMetadata = new HeadMetadata()
.setCmdInfo(cmdInfo)
// 请求命令类型: 0 心跳,1 业务; see ExternalMessageCmdCode
.setCmdCode(1);
requestMessage.setHeadMetadata(headMetadata);
if (Objects.nonNull(data)) {
requestMessage.setData(data);
}
}
/**
* 创建响应对象
*
* @param cmdInfo 路由地址
* @return ResponseMessage
*/
public ResponseMessage createResponseMessage(CmdInfo cmdInfo) {
ResponseMessage responseMessage = new ResponseMessage();
// 元信息 路由地址
responseMessage.setHeadMetadata(new HeadMetadata().setCmdInfo(cmdInfo));
return responseMessage;
}
/**
* 创建响应对象
*
* @param cmdInfo 路由地址
* @param bizData 业务数据
* @return ResponseMessage
*/
public ResponseMessage createResponseMessage(CmdInfo cmdInfo, Object bizData) {
Objects.requireNonNull(bizData);
ResponseMessage responseMessage = createResponseMessage(cmdInfo);
// 业务数据
responseMessage.setData(bizData);
return responseMessage;
}
}
================================================
FILE: common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeleton.java
================================================
/*
* ioGame
* Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.iohao.game.action.skeleton.core;
import com.iohao.game.action.skeleton.core.flow.*;
import com.iohao.game.action.skeleton.core.runner.Runners;
import com.iohao.game.common.kit.attr.AttrOptionDynamic;
import com.iohao.game.common.kit.attr.AttrOptions;
import com.iohao.game.common.kit.concurrent.executor.ExecutorRegion;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 整个核心的骨架.积木骷髅 (业务框架)
*