Compare commits
No commits in common. "master" and "release-please--branches--master--components--minotaur" have entirely different histories.
master
...
release-pl
|
@ -0,0 +1,2 @@
|
||||||
|
exclude:
|
||||||
|
- "**/*.md"
|
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 30 KiB |
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit bb9c3f74620c19b139d13c20793f9ff016d302c0
|
|
@ -0,0 +1,37 @@
|
||||||
|
title This is a title
|
||||||
|
|
||||||
|
participantspacing equal
|
||||||
|
#participantspacing gives manual control of spacing between participants, equal: equal distance between all participants, number i.e. 20.5: minimum space
|
||||||
|
|
||||||
|
actor Client#lightgreen
|
||||||
|
#supported participant types: participant, actor, boundary, control, entity, database
|
||||||
|
|
||||||
|
participantgroup #lightgreen Minotaur Server
|
||||||
|
participant Server
|
||||||
|
participant Shunt
|
||||||
|
abox left of Shunt: System 消息将全局单线程执行,Shunt 消息将会\n在连接当前所在分流渠道内执行。相同分流渠道的\n消息将串行处理,不同分流渠道消息并行处理。\n\n连接可根据业务场景灵活的通过 srv.UseShunt 来\n切换当前所处的分流渠道
|
||||||
|
abox left of Shunt: 异步消息\n\n(SystemMessage) srv.PushAsyncMessage\n(ShuntMessage) srv.PushShuntAsyncMessage\n(SystemMessage) srv.PushUniqueAsyncMessage\n(ShuntMessage) srv.PushUniqueShuntAsyncMessage\n\n Unique 消息将会在上一个相同消息未执行完毕\n的情况下忽略后续消息
|
||||||
|
end
|
||||||
|
|
||||||
|
Client->Server:通过 WebSocket、TCP、UDP、KCP 等协议与服务器建立连接
|
||||||
|
loop Write Loop
|
||||||
|
Server ->Client:写入数据包
|
||||||
|
abox left of Server: 数据包将被写入对应连接的缓冲区内等待发送 ,写入\n缓冲区后逻辑视为处理完毕,网络 IO 不会阻塞分流渠道
|
||||||
|
end
|
||||||
|
Server -->Shunt: (SystemMessage) OnConnectionOpenedEvent
|
||||||
|
Shunt --> Shunt: 消息处理
|
||||||
|
Server -->Shunt: (ShuntMessage) OnConnectionOpenedAfterEvent
|
||||||
|
Shunt --> Shunt: 消息处理
|
||||||
|
|
||||||
|
loop Read Loop
|
||||||
|
Client->Server:发送数据包
|
||||||
|
abox right of Client: 数据包将被发送到连接对应分流渠道的缓冲区内
|
||||||
|
Server -->Shunt: (ShuntMessage) OnConnectionReceivePacketEvent
|
||||||
|
Shunt --> Shunt: 消息处理
|
||||||
|
Shunt --> Server: 回复消息
|
||||||
|
Server --> Server: 写入 Write Loop
|
||||||
|
end
|
||||||
|
|
||||||
|
Client <->Server: 断开或关闭连接
|
||||||
|
Server -->Shunt: (ShuntMessage) OnConnectionClosedEvent
|
||||||
|
Shunt --> Shunt: 消息处理
|
|
@ -0,0 +1,61 @@
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
# This workflow checks out code, performs a Codacy security scan
|
||||||
|
# and integrates the results with the
|
||||||
|
# GitHub Advanced Security code scanning feature. For more information on
|
||||||
|
# the Codacy security scan action usage and parameters, see
|
||||||
|
# https://github.com/codacy/codacy-analysis-cli-action.
|
||||||
|
# For more information on Codacy Analysis CLI in general, see
|
||||||
|
# https://github.com/codacy/codacy-analysis-cli.
|
||||||
|
|
||||||
|
name: Codacy Security Scan
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "master" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '43 2 * * 4'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
codacy-security-scan:
|
||||||
|
permissions:
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||||
|
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||||
|
name: Codacy Security Scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Checkout the repository to the GitHub Actions runner
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||||
|
- name: Run Codacy Analysis CLI
|
||||||
|
uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b
|
||||||
|
with:
|
||||||
|
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
|
||||||
|
# You can also omit the token and run the tools that support default configurations
|
||||||
|
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||||
|
verbose: true
|
||||||
|
output: results.sarif
|
||||||
|
format: sarif
|
||||||
|
# Adjust severity of non-security issues
|
||||||
|
gh-code-scanning-compat: true
|
||||||
|
# Force 0 exit code to allow SARIF file generation
|
||||||
|
# This will handover control about PR rejection to the GitHub side
|
||||||
|
max-allowed-issues: 2147483647
|
||||||
|
|
||||||
|
# Upload the SARIF file generated in the previous step
|
||||||
|
- name: Upload SARIF results file
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: results.sarif
|
|
@ -0,0 +1,76 @@
|
||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "master" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '39 18 * * 6'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||||
|
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||||
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Dependency Review Action
|
||||||
|
#
|
||||||
|
# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
|
||||||
|
#
|
||||||
|
# Source repository: https://github.com/actions/dependency-review-action
|
||||||
|
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
|
||||||
|
name: 'Dependency Review'
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dependency-review:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout Repository'
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: 'Dependency Review'
|
||||||
|
uses: actions/dependency-review-action@v2
|
|
@ -0,0 +1,21 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-please:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: GoogleCloudPlatform/release-please-action@v3
|
||||||
|
id: release
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
release-type: go
|
||||||
|
package-name: minotaur
|
||||||
|
bump-minor-pre-major: true
|
||||||
|
bump-patch-for-minor-pre-major: true
|
||||||
|
changelog-types: '[{"type":"other","section":"Other | 其他更改","hidden":false},{"type":"revert","section":"Reverts | 回退","hidden":false},{"type":"feat","section":"Features | 新特性","hidden":false},{"type":"fix","section":"Bug Fixes | 修复","hidden":false},{"type":"improvement","section":"Feature Improvements | 改进","hidden":false},{"type":"docs","section":"Docs | 文档优化","hidden":false},{"type":"style","section":"Styling | 可读性优化","hidden":false},{"type":"refactor","section":"Code Refactoring | 重构","hidden":false},{"type":"perf","section":"Performance Improvements | 性能优化","hidden":false},{"type":"test","section":"Tests | 新增或优化测试用例","hidden":false},{"type":"build","section":"Build System | 影响构建的修改","hidden":false},{"type":"ci","section":"CI | 更改我们的 CI 配置文件和脚本","hidden":false}]'
|
||||||
|
# release-as: 0.5.0
|
|
@ -0,0 +1,168 @@
|
||||||
|
### Minotaur
|
||||||
|
old/app/monopoly/
|
||||||
|
|
||||||
|
### VisualStudioCode template
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Linux template
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### Windows template
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
### macOS template
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
.refer
|
||||||
|
.refer/*
|
|
@ -0,0 +1,128 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
kercylan@gmail.com.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
|
@ -0,0 +1,36 @@
|
||||||
|
# 贡献指南
|
||||||
|
|
||||||
|
感谢您对 Minotaur 项目的兴趣!
|
||||||
|
|
||||||
|
欢迎任何人的贡献,无论是新手还是有经验的开发者。以下是一些关于如何开始贡献的指导。
|
||||||
|
|
||||||
|
## 开始之前
|
||||||
|
|
||||||
|
1. **了解项目**:请先阅读项目的 [README.md](README.md) 和其他文档,了解项目的目的和结构。
|
||||||
|
2. **遵循行为准则**:请确保您的贡献符合我们的 [行为准则](CODE_OF_CONDUCT.md)。
|
||||||
|
|
||||||
|
## 报告问题
|
||||||
|
|
||||||
|
发现了问题或有新的想法?请通过 GitHub Issues 提交。
|
||||||
|
|
||||||
|
1. **查找现有问题**:在提交之前,请搜索现有的问题,看看是否有人已经报告了相同的问题。
|
||||||
|
2. **提供详细信息**:在报告问题时,请提供尽可能多的详细信息,包括错误消息、操作系统、版本号等。
|
||||||
|
|
||||||
|
## 贡献代码
|
||||||
|
|
||||||
|
想要为 Minotaur 项目贡献代码?请按照以下步骤操作:
|
||||||
|
|
||||||
|
1. **Fork 项目**:点击 GitHub 页面右上角的 "Fork" 按钮。
|
||||||
|
2. **克隆您的 Fork**:在您的本地计算机上克隆您的 Fork。
|
||||||
|
3. **创建新分支**:为您的修改创建一个新分支。
|
||||||
|
4. **提交您的更改**:在新分支上进行修改,并提交您的更改。
|
||||||
|
5. **发送 Pull Request**:返回您的 Fork 在 GitHub 上的页面,并点击 "New Pull Request" 按钮。
|
||||||
|
|
||||||
|
## 代码风格
|
||||||
|
|
||||||
|
请确保您的代码符合项目的编码风格和规范。
|
||||||
|
|
||||||
|
## 联系我们
|
||||||
|
|
||||||
|
如果您有任何问题或需要帮助,请通过以下方式与我取得联系:
|
||||||
|
- 发送电子邮件至:kercylan@gmail.com
|
|
@ -1,150 +0,0 @@
|
||||||
Originally published at https://rakyll.org/coredumps/.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
Debugging is highly useful to examine the execution flow
|
|
||||||
and to understand the current state of a program.
|
|
||||||
|
|
||||||
A core file is a file that contains the memory dump of a running
|
|
||||||
process and its process status. It is primarily used for post-mortem
|
|
||||||
debugging of a program, as well as to understand a program's state
|
|
||||||
while it is still running. These two cases make debugging of core dumps
|
|
||||||
a good diagnostics aid to postmortem and analyze production
|
|
||||||
services.
|
|
||||||
|
|
||||||
I will use a simple hello world web server in this article,
|
|
||||||
but in real life our programs might get very
|
|
||||||
complicated easily.
|
|
||||||
The availability of core dump analysis gives you an
|
|
||||||
opportunity to resurrect a program from specific snapshot
|
|
||||||
and look into cases that might only reproducible in certain
|
|
||||||
conditions/environments.
|
|
||||||
|
|
||||||
__Note__: This flow only works on Linux at this point end-to-end,
|
|
||||||
I am not quite sure about the other Unixes but it is not
|
|
||||||
yet supported on macOS. Windows is not supported at this point.
|
|
||||||
|
|
||||||
Before we begin, you need to make sure that your ulimit
|
|
||||||
for core dumps are at a reasonable level. It is by default
|
|
||||||
0 which means the max core file size can only be zero.
|
|
||||||
I usually set it to unlimited on my development machine by typing:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ulimit -c unlimited
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, make sure you have [delve](https://github.com/derekparker/delve)
|
|
||||||
installed on your machine.
|
|
||||||
|
|
||||||
Here is a `main.go` that contains a simple handler and it starts an HTTP server.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cat main.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprint(w, "hello world\n")
|
|
||||||
})
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:7777", nil))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's build this and have a binary.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go build .
|
|
||||||
```
|
|
||||||
|
|
||||||
Let’s assume, in the future, there is something messy going on with
|
|
||||||
this server but you are not so sure about what it might be.
|
|
||||||
You might have instrumented your program in various ways but it
|
|
||||||
might not be enough for getting any clue from the existing
|
|
||||||
instrumentation data.
|
|
||||||
|
|
||||||
Basically, in a situation like this, it would be nice to have a
|
|
||||||
snapshot of the current process, and then use that snapshot to dive
|
|
||||||
into to the current state of your program with your existing debugging
|
|
||||||
tools.
|
|
||||||
|
|
||||||
There are several ways to obtain a core file. You might have been
|
|
||||||
already familiar with crash dumps, these are basically core dumps
|
|
||||||
written to disk when a program is crashing. Go doesn't enable crash dumps
|
|
||||||
by default but gives you this option on Ctrl+backslash when
|
|
||||||
`GOTRACEBACK` env variable is set to "crash".
|
|
||||||
|
|
||||||
```
|
|
||||||
$ GOTRACEBACK=crash ./hello
|
|
||||||
(Ctrl+\)
|
|
||||||
```
|
|
||||||
|
|
||||||
It will crash the program with stack trace printed and core dump file
|
|
||||||
will be written.
|
|
||||||
|
|
||||||
Another option is to retrieve a core dump from a running process
|
|
||||||
without having to kill a process.
|
|
||||||
With `gcore`, it is possible to get the core
|
|
||||||
files without crashing. Let’s start the server again:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./hello &
|
|
||||||
$ gcore 546 # 546 is the PID of hello.
|
|
||||||
```
|
|
||||||
We have a dump without crashing the process. The next step
|
|
||||||
is to load the core file to delve and start analyzing.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ dlv core ./hello core.546
|
|
||||||
```
|
|
||||||
|
|
||||||
Alright, this is it! This is no different than the typical delve interactive.
|
|
||||||
You can backtrace, list, see variables, and more. Some features will be disabled
|
|
||||||
given a core dump is a snapshot and not a currently running process, but
|
|
||||||
the execution flow and the program state will be entirely accessible.
|
|
||||||
|
|
||||||
```
|
|
||||||
(dlv) bt
|
|
||||||
0 0x0000000000457774 in runtime.raise
|
|
||||||
at /usr/lib/go/src/runtime/sys_linux_amd64.s:110
|
|
||||||
1 0x000000000043f7fb in runtime.dieFromSignal
|
|
||||||
at /usr/lib/go/src/runtime/signal_unix.go:323
|
|
||||||
2 0x000000000043f9a1 in runtime.crash
|
|
||||||
at /usr/lib/go/src/runtime/signal_unix.go:409
|
|
||||||
3 0x000000000043e982 in runtime.sighandler
|
|
||||||
at /usr/lib/go/src/runtime/signal_sighandler.go:129
|
|
||||||
4 0x000000000043f2d1 in runtime.sigtrampgo
|
|
||||||
at /usr/lib/go/src/runtime/signal_unix.go:257
|
|
||||||
5 0x00000000004579d3 in runtime.sigtramp
|
|
||||||
at /usr/lib/go/src/runtime/sys_linux_amd64.s:262
|
|
||||||
6 0x00007ff68afec330 in (nil)
|
|
||||||
at :0
|
|
||||||
7 0x000000000040f2d6 in runtime.notetsleep
|
|
||||||
at /usr/lib/go/src/runtime/lock_futex.go:209
|
|
||||||
8 0x0000000000435be5 in runtime.sysmon
|
|
||||||
at /usr/lib/go/src/runtime/proc.go:3866
|
|
||||||
9 0x000000000042ee2e in runtime.mstart1
|
|
||||||
at /usr/lib/go/src/runtime/proc.go:1182
|
|
||||||
10 0x000000000042ed04 in runtime.mstart
|
|
||||||
at /usr/lib/go/src/runtime/proc.go:1152
|
|
||||||
|
|
||||||
(dlv) ls
|
|
||||||
> runtime.raise() /usr/lib/go/src/runtime/sys_linux_amd64.s:110 (PC: 0x457774)
|
|
||||||
105: SYSCALL
|
|
||||||
106: MOVL AX, DI // arg 1 tid
|
|
||||||
107: MOVL sig+0(FP), SI // arg 2
|
|
||||||
108: MOVL $200, AX // syscall - tkill
|
|
||||||
109: SYSCALL
|
|
||||||
=> 110: RET
|
|
||||||
111:
|
|
||||||
112: TEXT runtime·raiseproc(SB),NOSPLIT,$0
|
|
||||||
113: MOVL $39, AX // syscall - getpid
|
|
||||||
114: SYSCALL
|
|
||||||
115: MOVL AX, DI // arg 1 pid
|
|
||||||
```
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 kercylan98
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
331
README.md
|
@ -1,2 +1,331 @@
|
||||||
# vRp.CD2g_test
|
# Minotaur
|
||||||
|
|
||||||
|
Minotaur 是一个用于服务端开发的支持库,其中采用了大量泛型设计,主要被用于游戏服务器开发,但由于拥有大量通用的功能,也常被用于 WEB 开发。
|
||||||
|
***
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
<a target="_blank" href="https://goreportcard.com/report/github.com/kercylan98/minotaur"><img src="https://goreportcard.com/badge/github.com/kercylan98/minotaur?style=flat-square" /></a>
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
> - 这是支持快速搭建多功能游戏服务器及 HTTP 服务器的 `Golang` 服务端框架;
|
||||||
|
> - 网络传输基于 [`gorilla/websocket`](https://github.com/gorilla/websocket)、[`gin-gonic/gin`](https://github.com/gin-gonic/gin)、[`grpc/grpc-go`](https://github.com/grpc/grpc-go)、[`panjf2000/gnet`](https://github.com/panjf2000/gnet)、[`xtaci/kcp-go`](https://github.com/xtaci/kcp-go) 构建;
|
||||||
|
> - 该项目的目标是提供一个简单、高效、可扩展的游戏服务器框架,让开发者可以专注于游戏逻辑的开发,而不用花费大量时间在网络传输、配置导表、日志、监控等基础功能的开发上;
|
||||||
|
|
||||||
|
***
|
||||||
|
在 Minotaur 中不包括任何跨服实现,但支持基于多级路由器快速实现跨服功能。推荐使用 [`NATS.io`](https://nats.io/) 作为跨服消息中间件。
|
||||||
|
- 目前已实践的弹幕游戏项目以 `NATS.io` 作为消息队列,实现了跨服、埋点日志收集等功能,部署在 `Kubernetes` 集群中;
|
||||||
|
- 该项目客户端与服务端采用 `WebSocket` 进行通讯,服务端暴露 `HTTP` 接口接收互动数据消息回调,通过负载均衡器进入 `Kubernetes` 集群中的 `Minotaur` 服务,最终通过 `NATS.io` 消息队列转发至对应所在的 `Pod` 中进行处理;
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>关于 Pod 配置参数及非极限压测数据</summary>
|
||||||
|
|
||||||
|
> 本次压测 `Pod` 扩容数量为 1,但由于压测连接是最开始就建立好的,所以该扩容的 `Pod` 并没有接受到压力。
|
||||||
|
> 理论上来说该 `Pod` 也应该接受 `HTTP` 回调压力,实测过程中,这个扩容的 `Pod` 没有接受到任何压力
|
||||||
|
|
||||||
|
**Pod 配置参数**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**压测结果**
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
**监控数据**
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
## 特色内容
|
||||||
|
```mermaid
|
||||||
|
mindmap
|
||||||
|
root((Minotaur))
|
||||||
|
所有功能均为可选的,引入即用
|
||||||
|
基于动态分流的异步消息支持
|
||||||
|
兼容各类实现的帧同步组件
|
||||||
|
多级消息路由器,轻松搭建跨服服务
|
||||||
|
开箱即用的配置导表工具
|
||||||
|
强大的 utils 包
|
||||||
|
常用的切片、map、随机、时间、数学、文件、类型转换等工具函数
|
||||||
|
适用于定时、编排、组合、匹配、校验等行为的组件
|
||||||
|
版本比较、重试、耗时计数等(super package)
|
||||||
|
活动、任务、AOI、寻路、战斗、移动、房间等通用游戏组件
|
||||||
|
针对扑克牌玩法的大量支持
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server 架构预览
|
||||||
|

|
||||||
|
|
||||||
|
## 安装
|
||||||
|
注意:依赖于 **[Go](https://go.dev/) 1.20 +**
|
||||||
|
|
||||||
|
运行以下 Go 命令来安装软件包:`minotaur`
|
||||||
|
```sh
|
||||||
|
$ go get -u github.com/kercylan98/minotaur
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用法
|
||||||
|
- 在`Minotaur`中大量使用了 **[泛型](https://go.dev/doc/tutorial/generics)** 、 **[观察者(事件)](https://www.runoob.com/design-pattern/observer-pattern.html)** 和 **[选项模式](https://juejin.cn/post/6844903729313873927)**,在使用前建议先进行相应了解;
|
||||||
|
- 项目文档可访问 **[pkg.go.dev](https://pkg.go.dev/github.com/kercylan98/minotaur)** 进行查阅;
|
||||||
|
|
||||||
|
### 本地文档
|
||||||
|
可使用 `godoc` 搭建本地文档服务器
|
||||||
|
#### 安装 godoc
|
||||||
|
```shell
|
||||||
|
git clone golang.org/x/tools
|
||||||
|
cd tools/cmd
|
||||||
|
go install ...
|
||||||
|
```
|
||||||
|
#### 使用 `godoc` 启动本地文档服务器
|
||||||
|
```shell
|
||||||
|
godoc -http=:9998 -play
|
||||||
|
```
|
||||||
|
#### Windows
|
||||||
|
```shell
|
||||||
|
.\local-doc.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux or MacOS
|
||||||
|
```shell
|
||||||
|
chmod 777 ./local-doc.sh
|
||||||
|
./local-doc.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 文档地址
|
||||||
|
- **[http://localhost:9998/pkg/github.com/kercylan98/minotaur/](http://localhost:9998/pkg/github.com/kercylan98/minotaur/)**
|
||||||
|
- **[https://pkg.go.dev/github.com/kercylan98/minotaur](https://pkg.go.dev/github.com/kercylan98/minotaur)**
|
||||||
|
|
||||||
|
### 简单回响服务器
|
||||||
|
创建一个基于`Websocket`创建的单线程回响服务器。
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket)
|
||||||
|
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
|
||||||
|
conn.Write(packet)
|
||||||
|
})
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
访问 **[WebSocket 在线测试](http://www.websocket-test.com/)** 进行验证。
|
||||||
|
> Websocket地址: ws://127.0.0.1:9999
|
||||||
|
|
||||||
|
### 分流服务器
|
||||||
|
分流服务器可以将消息分流到不同的分组上,每个分组中为串行处理,不同分组之间并行处理。
|
||||||
|
|
||||||
|
> 关于分流服务器的思考:
|
||||||
|
> - 当游戏需要以房间的形式进行时,应该确保相同房间的玩家处于同一分流中,不同房间的玩家处于不同分流中,这样可以避免不同房间的玩家之间的消息互相阻塞;
|
||||||
|
> - 这时候网络 IO 应该根据不同的游戏类型而进行不同的处理,例如回合制可以同步执行,而实时游戏应该采用异步执行;
|
||||||
|
> - 当游戏大部分时候以单人游戏进行时,应该每个玩家处于自身唯一的分流中,此时非互动的消息造成的网络 IO 采用同步执行即可,也不会阻塞到其他玩家的消息处理;
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/server"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket)
|
||||||
|
srv.RegConnectionOpenedEvent(func(srv *server.Server, conn *server.Conn) {
|
||||||
|
// 通过 user_id 进行分流,不同用户的消息将不会互相阻塞
|
||||||
|
srv.UseShunt(conn, conn.Gata("user_id").(string))
|
||||||
|
})
|
||||||
|
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
|
||||||
|
var roomId = "default"
|
||||||
|
switch string(packet) {
|
||||||
|
case "JoinRoom":
|
||||||
|
// 将用户所处的分流渠道切换到 roomId 渠道,此刻同一分流渠道的消息将会按队列顺序处理
|
||||||
|
srv.UseShunt(conn, roomId)
|
||||||
|
case "LeaveRoom":
|
||||||
|
// 将用户所处分流切换为用户自身的分流渠道
|
||||||
|
srv.UseShunt(conn, conn.Gata("user_id").(string))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
> 该示例中模拟了用户分流渠道在自身渠道和房间渠道切换的过程,通过`UseShunt`对连接分流渠道进行设置,提高并发处理能力。
|
||||||
|
|
||||||
|
### 服务器死锁检测
|
||||||
|
`Minotaur`内置了服务器消息死锁检测功能,可通过`server.WithDeadlockDetect`进行开启。
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/server"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket,
|
||||||
|
server.WithDeadlockDetect(time.Second*5),
|
||||||
|
)
|
||||||
|
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
conn.Write(packet)
|
||||||
|
})
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
> 在开启死锁检测的时候需要设置一个合理的死锁怀疑时间,该时间内消息没有处理完毕则会触发死锁检测,并打印`WARN`级别的日志输出。
|
||||||
|
|
||||||
|
### 计时器
|
||||||
|
在默认的`server.Server`不会包含计时器功能,可通过`server.WithTicker`进行开启,例如:
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/server"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket, server.WithTicker(-1, 50, 10, false))
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
也可以通过`timer.GetTicker`获取计时器进行使用,例如:
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
|
"github.com/kercylan98/minotaur/utils/times"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var ticker = timer.GetTicker(10)
|
||||||
|
var wait sync.WaitGroup
|
||||||
|
wait.Add(3)
|
||||||
|
ticker.Loop("LOOP", timer.Instantly, times.Second, timer.Forever, func() {
|
||||||
|
fmt.Println("LOOP")
|
||||||
|
wait.Done()
|
||||||
|
})
|
||||||
|
wait.Wait()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
在分布式环境中,如果存在类似于多服务器需要同时间刷新配置时,可使用`Cron`表达式设置定时任务。
|
||||||
|
|
||||||
|
### 基于`xlsx`文件的配置导出工具
|
||||||
|
该导出器的`xlsx`文件配置使用`JSON`语法进行复杂类型配置,具体可参考图例
|
||||||
|
- **[`planner/pce/exporter`](planner/pce/exporter)** 是实现了基于`xlsx`文件的配置导出工具,可直接编译成可执行文件使用;
|
||||||
|
- **[`planner/pce/exporter/xlsx_template.xlsx`](planner/pce/exporter/xlsx_template.xlsx)** 是导出工具的模板文件,其中包含了具体的规则说明。
|
||||||
|
- 模板文件图例:
|
||||||
|

|
||||||
|
|
||||||
|
#### 导出 JSON 文件(可供客户端直接使用,包含索引的配置导出后为键值模式,可直接读取)
|
||||||
|
```text
|
||||||
|
Flags:
|
||||||
|
-e, --exclude string excluded configuration names or display names (comma separated) | 排除的配置名或显示名(英文逗号分隔)
|
||||||
|
-h, --help help for json
|
||||||
|
-o, --output string directory path of the output json file | 输出的 json 文件所在目录路径
|
||||||
|
-p, --prefix string export configuration file name prefix | 导出配置文件名前缀
|
||||||
|
-t, --type string export server configuration[s] or client configuration[c] | 导出服务端配置[s]还是客户端配置[c]
|
||||||
|
-f, --xlsx string xlsx file path or directory path | xlsx 文件路径或所在目录路径
|
||||||
|
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
expoter.exe json -t s -f xlsx_template.xlsx -o ./output
|
||||||
|
```
|
||||||
|
导出结果示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"1": {
|
||||||
|
"b": {
|
||||||
|
"Id": 1,
|
||||||
|
"Count": "b",
|
||||||
|
"Info": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "小明",
|
||||||
|
"info": {
|
||||||
|
"lv": 1,
|
||||||
|
"exp": {
|
||||||
|
"mux": 10,
|
||||||
|
"count": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Other": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "张飞"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "刘备"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 导出 Golang 文件
|
||||||
|
```text
|
||||||
|
Flags:
|
||||||
|
-e, --exclude string excluded configuration names or display names (comma separated) | 排除的配置名或显示名(英文逗号分隔)
|
||||||
|
-h, --help help for go
|
||||||
|
-o, --output string output path | 输出的 go 文件路径
|
||||||
|
-f, --xlsx string xlsx file path or directory path | xlsx 文件路径或所在目录路径
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
expoter.exe go -f xlsx_template.xlsx -o ./output
|
||||||
|
```
|
||||||
|
使用示例
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(config.EasyConfig.Id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 持续更新的示例项目
|
||||||
|
- **[Minotaur-Example](https://github.com/kercylan98/minotaur-example)**
|
||||||
|
|
||||||
|
### 贡献者列表
|
||||||
|
<a href="https://github.com/kercylan98/minotaur/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=kercylan98/minotaur" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
#### 参与贡献请参考 **[CONTRIBUTING.md](CONTRIBUTING.md)** 贡献指南。
|
||||||
|
|
||||||
|
### 联系方式
|
||||||
|
- **[Email: kercylan@gmail.com](mailto:kercylan@gmail.com)**
|
||||||
|
- **[Telegram: ziv_siren](https://telegram.me/ziv_siren)**
|
||||||
|
|
||||||
|
# JetBrains OS licenses
|
||||||
|
|
||||||
|
`Minotaur` had been being developed with `GoLand` IDE under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here.
|
||||||
|
|
||||||
|
<a href="https://www.jetbrains.com/?from=minotaur" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png?_gl=1*1vt713y*_ga*MTEzMjEzODQxNC4xNjc5OTY3ODUw*_ga_9J976DJZ68*MTY4ODU0MDUyMy4yMC4xLjE2ODg1NDA5NDAuMjUuMC4w&_ga=2.261225293.1519421387.1688540524-1132138414.1679967850" width="250" align="middle"/></a>
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
configuration 基于配置导表功能实现的配置加载及刷新功能
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 包级函数定义
|
||||||
|
|
||||||
|
|函数名称|描述
|
||||||
|
|:--|:--
|
||||||
|
|[Init](#Init)|配置初始化
|
||||||
|
|[Load](#Load)|加载配置
|
||||||
|
|[Refresh](#Refresh)|刷新配置
|
||||||
|
|[WithTickerLoad](#WithTickerLoad)|通过定时器加载配置
|
||||||
|
|[StopTickerLoad](#StopTickerLoad)|停止通过定时器加载配置
|
||||||
|
|[RegConfigRefreshEvent](#RegConfigRefreshEvent)|当配置刷新时将立即执行被注册的事件处理函数
|
||||||
|
|[OnConfigRefreshEvent](#OnConfigRefreshEvent)|暂无描述...
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[RefreshEventHandle](#struct_RefreshEventHandle)|配置刷新事件处理函数
|
||||||
|
|`INTERFACE`|[Loader](#struct_Loader)|配置加载器
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
#### func Init(loader ...Loader)
|
||||||
|
<span id="Init"></span>
|
||||||
|
> 配置初始化
|
||||||
|
> - 在初始化后会立即加载配置
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func Load()
|
||||||
|
<span id="Load"></span>
|
||||||
|
> 加载配置
|
||||||
|
> - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func Refresh()
|
||||||
|
<span id="Refresh"></span>
|
||||||
|
> 刷新配置
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func WithTickerLoad(ticker *timer.Ticker, interval time.Duration)
|
||||||
|
<span id="WithTickerLoad"></span>
|
||||||
|
> 通过定时器加载配置
|
||||||
|
> - 通过定时器加载配置后,会自动刷新线上配置
|
||||||
|
> - 调用该函数后不会立即刷新,而是在 interval 后加载并刷新一次配置,之后每隔 interval 加载并刷新一次配置
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func StopTickerLoad()
|
||||||
|
<span id="StopTickerLoad"></span>
|
||||||
|
> 停止通过定时器加载配置
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegConfigRefreshEvent(handle RefreshEventHandle)
|
||||||
|
<span id="RegConfigRefreshEvent"></span>
|
||||||
|
> 当配置刷新时将立即执行被注册的事件处理函数
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnConfigRefreshEvent()
|
||||||
|
<span id="OnConfigRefreshEvent"></span>
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RefreshEventHandle"></span>
|
||||||
|
### RefreshEventHandle `STRUCT`
|
||||||
|
配置刷新事件处理函数
|
||||||
|
```go
|
||||||
|
type RefreshEventHandle func()
|
||||||
|
```
|
||||||
|
<span id="struct_Loader"></span>
|
||||||
|
### Loader `INTERFACE`
|
||||||
|
配置加载器
|
||||||
|
```go
|
||||||
|
type Loader interface {
|
||||||
|
Load()
|
||||||
|
Refresh()
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,73 @@
|
||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/log"
|
||||||
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tickerLoadRefresh = "_tickerLoadRefresh"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cTicker *timer.Ticker
|
||||||
|
cInterval time.Duration
|
||||||
|
cLoader []Loader
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init 配置初始化
|
||||||
|
// - 在初始化后会立即加载配置
|
||||||
|
func Init(loader ...Loader) {
|
||||||
|
cLoader = loader
|
||||||
|
Load()
|
||||||
|
Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load 加载配置
|
||||||
|
// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新
|
||||||
|
func Load() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("Config", log.String("Action", "Load"), log.Err(err.(error)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, loader := range cLoader {
|
||||||
|
loader.Load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh 刷新配置
|
||||||
|
func Refresh() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("Config", log.String("Action", "Refresh"), log.Err(err.(error)))
|
||||||
|
}
|
||||||
|
OnConfigRefreshEvent()
|
||||||
|
}()
|
||||||
|
for _, loader := range cLoader {
|
||||||
|
loader.Refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTickerLoad 通过定时器加载配置
|
||||||
|
// - 通过定时器加载配置后,会自动刷新线上配置
|
||||||
|
// - 调用该函数后不会立即刷新,而是在 interval 后加载并刷新一次配置,之后每隔 interval 加载并刷新一次配置
|
||||||
|
func WithTickerLoad(ticker *timer.Ticker, interval time.Duration) {
|
||||||
|
if ticker != cTicker && cTicker != nil {
|
||||||
|
cTicker.StopTimer(tickerLoadRefresh)
|
||||||
|
}
|
||||||
|
cTicker = ticker
|
||||||
|
cInterval = interval
|
||||||
|
cTicker.Loop(tickerLoadRefresh, cInterval, cInterval, timer.Forever, func() {
|
||||||
|
Load()
|
||||||
|
Refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTickerLoad 停止通过定时器加载配置
|
||||||
|
func StopTickerLoad() {
|
||||||
|
if cTicker != nil {
|
||||||
|
cTicker.StopTimer(tickerLoadRefresh)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package configuration 基于配置导表功能实现的配置加载及刷新功能
|
||||||
|
package configuration
|
|
@ -0,0 +1,17 @@
|
||||||
|
package configuration
|
||||||
|
|
||||||
|
// RefreshEventHandle 配置刷新事件处理函数
|
||||||
|
type RefreshEventHandle func()
|
||||||
|
|
||||||
|
var configRefreshEventHandles []func()
|
||||||
|
|
||||||
|
// RegConfigRefreshEvent 当配置刷新时将立即执行被注册的事件处理函数
|
||||||
|
func RegConfigRefreshEvent(handle RefreshEventHandle) {
|
||||||
|
configRefreshEventHandles = append(configRefreshEventHandles, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnConfigRefreshEvent() {
|
||||||
|
for _, handle := range configRefreshEventHandles {
|
||||||
|
handle()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package configuration
|
||||||
|
|
||||||
|
// Loader 配置加载器
|
||||||
|
type Loader interface {
|
||||||
|
// Load 加载配置
|
||||||
|
// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新
|
||||||
|
Load()
|
||||||
|
// Refresh 刷新线上配置
|
||||||
|
Refresh()
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Main
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/server"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket,
|
||||||
|
server.WithDeadlockDetect(time.Second*5),
|
||||||
|
)
|
||||||
|
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
conn.Write(packet)
|
||||||
|
})
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Main
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,18 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket)
|
||||||
|
srv.RegConnectionOpenedEvent(func(srv *server.Server, conn *server.Conn) {
|
||||||
|
srv.UseShunt(conn, conn.GetData("room_id").(string))
|
||||||
|
})
|
||||||
|
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
|
||||||
|
conn.Write(packet)
|
||||||
|
})
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Main
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,13 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/server"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket)
|
||||||
|
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
|
||||||
|
conn.Write(packet)
|
||||||
|
})
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Main
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/server"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
srv := server.New(server.NetworkWebsocket, server.WithTicker(-1, 50, 10, false))
|
||||||
|
if err := srv.Run(":9999"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Main
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
|
"github.com/kercylan98/minotaur/utils/times"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var ticker = timer.GetTicker(10)
|
||||||
|
var wait sync.WaitGroup
|
||||||
|
wait.Add(3)
|
||||||
|
ticker.Loop("LOOP", timer.Instantly, times.Second, timer.Forever, func() {
|
||||||
|
fmt.Println("LOOP")
|
||||||
|
wait.Done()
|
||||||
|
})
|
||||||
|
wait.Wait()
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Game
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
game 目录下包含了各类通用的游戏玩法性内容,其中该目录主要为基础性内容,具体目录将对应不同的游戏功能性内容。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,376 @@
|
||||||
|
# Activity
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
activity 活动状态管理
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 包级函数定义
|
||||||
|
|
||||||
|
|函数名称|描述
|
||||||
|
|:--|:--
|
||||||
|
|[SetTicker](#SetTicker)|设置自定义定时器,该方法必须在使用活动系统前调用,且只能调用一次
|
||||||
|
|[LoadGlobalData](#LoadGlobalData)|加载所有活动全局数据
|
||||||
|
|[LoadEntityData](#LoadEntityData)|加载所有活动实体数据
|
||||||
|
|[LoadOrRefreshActivity](#LoadOrRefreshActivity)|加载或刷新活动
|
||||||
|
|[DefineNoneDataActivity](#DefineNoneDataActivity)|声明无数据的活动类型
|
||||||
|
|[DefineGlobalDataActivity](#DefineGlobalDataActivity)|声明拥有全局数据的活动类型
|
||||||
|
|[DefineEntityDataActivity](#DefineEntityDataActivity)|声明拥有实体数据的活动类型
|
||||||
|
|[DefineGlobalAndEntityDataActivity](#DefineGlobalAndEntityDataActivity)|声明拥有全局数据和实体数据的活动类型
|
||||||
|
|[RegUpcomingEvent](#RegUpcomingEvent)|注册即将开始的活动事件处理器
|
||||||
|
|[OnUpcomingEvent](#OnUpcomingEvent)|即将开始的活动事件
|
||||||
|
|[RegStartedEvent](#RegStartedEvent)|注册活动开始事件处理器
|
||||||
|
|[OnStartedEvent](#OnStartedEvent)|活动开始事件
|
||||||
|
|[RegEndedEvent](#RegEndedEvent)|注册活动结束事件处理器
|
||||||
|
|[OnEndedEvent](#OnEndedEvent)|活动结束事件
|
||||||
|
|[RegExtendedShowStartedEvent](#RegExtendedShowStartedEvent)|注册活动结束后延长展示开始事件处理器
|
||||||
|
|[OnExtendedShowStartedEvent](#OnExtendedShowStartedEvent)|活动结束后延长展示开始事件
|
||||||
|
|[RegExtendedShowEndedEvent](#RegExtendedShowEndedEvent)|注册活动结束后延长展示结束事件处理器
|
||||||
|
|[OnExtendedShowEndedEvent](#OnExtendedShowEndedEvent)|活动结束后延长展示结束事件
|
||||||
|
|[RegNewDayEvent](#RegNewDayEvent)|注册新的一天事件处理器
|
||||||
|
|[OnNewDayEvent](#OnNewDayEvent)|新的一天事件
|
||||||
|
|[NewOptions](#NewOptions)|创建活动选项
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[Activity](#struct_Activity)|活动描述
|
||||||
|
|`STRUCT`|[Controller](#struct_Controller)|活动控制器
|
||||||
|
|`INTERFACE`|[BasicActivityController](#struct_BasicActivityController)|暂无描述...
|
||||||
|
|`INTERFACE`|[NoneDataActivityController](#struct_NoneDataActivityController)|无数据活动控制器
|
||||||
|
|`INTERFACE`|[GlobalDataActivityController](#struct_GlobalDataActivityController)|全局数据活动控制器
|
||||||
|
|`INTERFACE`|[EntityDataActivityController](#struct_EntityDataActivityController)|实体数据活动控制器
|
||||||
|
|`INTERFACE`|[GlobalAndEntityDataActivityController](#struct_GlobalAndEntityDataActivityController)|全局数据和实体数据活动控制器
|
||||||
|
|`STRUCT`|[DataMeta](#struct_DataMeta)|全局活动数据
|
||||||
|
|`STRUCT`|[EntityDataMeta](#struct_EntityDataMeta)|活动实体数据
|
||||||
|
|`STRUCT`|[UpcomingEventHandler](#struct_UpcomingEventHandler)|暂无描述...
|
||||||
|
|`STRUCT`|[Options](#struct_Options)|活动选项
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
#### func SetTicker(size int, options ...timer.Option)
|
||||||
|
<span id="SetTicker"></span>
|
||||||
|
> 设置自定义定时器,该方法必须在使用活动系统前调用,且只能调用一次
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func LoadGlobalData(handler func (activityType any))
|
||||||
|
<span id="LoadGlobalData"></span>
|
||||||
|
> 加载所有活动全局数据
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func LoadEntityData(handler func (activityType any))
|
||||||
|
<span id="LoadEntityData"></span>
|
||||||
|
> 加载所有活动实体数据
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func LoadOrRefreshActivity\[Type generic.Basic, ID generic.Basic\](activityType Type, activityId ID, options ...*Options) error
|
||||||
|
<span id="LoadOrRefreshActivity"></span>
|
||||||
|
> 加载或刷新活动
|
||||||
|
> - 通常在活动配置刷新时候将活动通过该方法注册或刷新
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func DefineNoneDataActivity\[Type generic.Basic, ID generic.Basic\](activityType Type) NoneDataActivityController[Type, ID, *none, none, *none]
|
||||||
|
<span id="DefineNoneDataActivity"></span>
|
||||||
|
> 声明无数据的活动类型
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func DefineGlobalDataActivity\[Type generic.Basic, ID generic.Basic, Data any\](activityType Type) GlobalDataActivityController[Type, ID, Data, none, *none]
|
||||||
|
<span id="DefineGlobalDataActivity"></span>
|
||||||
|
> 声明拥有全局数据的活动类型
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func DefineEntityDataActivity\[Type generic.Basic, ID generic.Basic, EntityID generic.Basic, EntityData any\](activityType Type) EntityDataActivityController[Type, ID, *none, EntityID, EntityData]
|
||||||
|
<span id="DefineEntityDataActivity"></span>
|
||||||
|
> 声明拥有实体数据的活动类型
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func DefineGlobalAndEntityDataActivity\[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any\](activityType Type) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
<span id="DefineGlobalAndEntityDataActivity"></span>
|
||||||
|
> 声明拥有全局数据和实体数据的活动类型
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegUpcomingEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler UpcomingEventHandler[ID], priority ...int)
|
||||||
|
<span id="RegUpcomingEvent"></span>
|
||||||
|
> 注册即将开始的活动事件处理器
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnUpcomingEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID])
|
||||||
|
<span id="OnUpcomingEvent"></span>
|
||||||
|
> 即将开始的活动事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegStartedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler StartedEventHandler[ID], priority ...int)
|
||||||
|
<span id="RegStartedEvent"></span>
|
||||||
|
> 注册活动开始事件处理器
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnStartedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID])
|
||||||
|
<span id="OnStartedEvent"></span>
|
||||||
|
> 活动开始事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegEndedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler EndedEventHandler[ID], priority ...int)
|
||||||
|
<span id="RegEndedEvent"></span>
|
||||||
|
> 注册活动结束事件处理器
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnEndedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID])
|
||||||
|
<span id="OnEndedEvent"></span>
|
||||||
|
> 活动结束事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegExtendedShowStartedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int)
|
||||||
|
<span id="RegExtendedShowStartedEvent"></span>
|
||||||
|
> 注册活动结束后延长展示开始事件处理器
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnExtendedShowStartedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID])
|
||||||
|
<span id="OnExtendedShowStartedEvent"></span>
|
||||||
|
> 活动结束后延长展示开始事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegExtendedShowEndedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int)
|
||||||
|
<span id="RegExtendedShowEndedEvent"></span>
|
||||||
|
> 注册活动结束后延长展示结束事件处理器
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnExtendedShowEndedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID])
|
||||||
|
<span id="OnExtendedShowEndedEvent"></span>
|
||||||
|
> 活动结束后延长展示结束事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegNewDayEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler NewDayEventHandler[ID], priority ...int)
|
||||||
|
<span id="RegNewDayEvent"></span>
|
||||||
|
> 注册新的一天事件处理器
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnNewDayEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID])
|
||||||
|
<span id="OnNewDayEvent"></span>
|
||||||
|
> 新的一天事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func NewOptions() *Options
|
||||||
|
<span id="NewOptions"></span>
|
||||||
|
> 创建活动选项
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Activity"></span>
|
||||||
|
### Activity `STRUCT`
|
||||||
|
活动描述
|
||||||
|
```go
|
||||||
|
type Activity[Type generic.Basic, ID generic.Basic] struct {
|
||||||
|
id ID
|
||||||
|
t Type
|
||||||
|
options *Options
|
||||||
|
state byte
|
||||||
|
lazy bool
|
||||||
|
tickerKey string
|
||||||
|
retention time.Duration
|
||||||
|
retentionKey string
|
||||||
|
mutex sync.RWMutex
|
||||||
|
getLastNewDayTime func() time.Time
|
||||||
|
setLastNewDayTime func(time.Time)
|
||||||
|
clearData func()
|
||||||
|
initializeData func()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Controller"></span>
|
||||||
|
### Controller `STRUCT`
|
||||||
|
活动控制器
|
||||||
|
```go
|
||||||
|
type Controller[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] struct {
|
||||||
|
t Type
|
||||||
|
activities map[ID]*Activity[Type, ID]
|
||||||
|
globalData map[ID]*DataMeta[Data]
|
||||||
|
entityData map[ID]map[EntityID]*EntityDataMeta[EntityData]
|
||||||
|
entityTof reflect.Type
|
||||||
|
globalInit func(activityId ID, data *DataMeta[Data])
|
||||||
|
entityInit func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Controller_GetGlobalData"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) GetGlobalData(activityId ID) Data
|
||||||
|
> 获取特定活动全局数据
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_GetEntityData"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) GetEntityData(activityId ID, entityId EntityID) EntityData
|
||||||
|
> 获取特定活动实体数据
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_IsOpen"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) IsOpen(activityId ID) bool
|
||||||
|
> 活动是否开启
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_IsShow"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) IsShow(activityId ID) bool
|
||||||
|
> 活动是否展示
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_IsOpenOrShow"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) IsOpenOrShow(activityId ID) bool
|
||||||
|
> 活动是否开启或展示
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_Refresh"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) Refresh(activityId ID)
|
||||||
|
> 刷新活动
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_InitializeNoneData"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) InitializeNoneData(handler func (activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_InitializeGlobalData"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) InitializeGlobalData(handler func (activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_InitializeEntityData"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) InitializeEntityData(handler func (activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Controller_InitializeGlobalAndEntityData"></span>
|
||||||
|
|
||||||
|
#### func (*Controller) InitializeGlobalAndEntityData(handler func (activityId ID, data *DataMeta[Data]), entityHandler func (activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_BasicActivityController"></span>
|
||||||
|
### BasicActivityController `INTERFACE`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type BasicActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
IsOpen(activityId ID) bool
|
||||||
|
IsShow(activityId ID) bool
|
||||||
|
IsOpenOrShow(activityId ID) bool
|
||||||
|
Refresh(activityId ID)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_NoneDataActivityController"></span>
|
||||||
|
### NoneDataActivityController `INTERFACE`
|
||||||
|
无数据活动控制器
|
||||||
|
```go
|
||||||
|
type NoneDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
InitializeNoneData(handler func(activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_GlobalDataActivityController"></span>
|
||||||
|
### GlobalDataActivityController `INTERFACE`
|
||||||
|
全局数据活动控制器
|
||||||
|
```go
|
||||||
|
type GlobalDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
GetGlobalData(activityId ID) Data
|
||||||
|
InitializeGlobalData(handler func(activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_EntityDataActivityController"></span>
|
||||||
|
### EntityDataActivityController `INTERFACE`
|
||||||
|
实体数据活动控制器
|
||||||
|
```go
|
||||||
|
type EntityDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
GetEntityData(activityId ID, entityId EntityID) EntityData
|
||||||
|
InitializeEntityData(handler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_GlobalAndEntityDataActivityController"></span>
|
||||||
|
### GlobalAndEntityDataActivityController `INTERFACE`
|
||||||
|
全局数据和实体数据活动控制器
|
||||||
|
```go
|
||||||
|
type GlobalAndEntityDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
GetGlobalData(activityId ID) Data
|
||||||
|
GetEntityData(activityId ID, entityId EntityID) EntityData
|
||||||
|
InitializeGlobalAndEntityData(handler func(activityId ID, data *DataMeta[Data]), entityHandler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_DataMeta"></span>
|
||||||
|
### DataMeta `STRUCT`
|
||||||
|
全局活动数据
|
||||||
|
```go
|
||||||
|
type DataMeta[Data any] struct {
|
||||||
|
once sync.Once
|
||||||
|
Data Data
|
||||||
|
LastNewDay time.Time
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_EntityDataMeta"></span>
|
||||||
|
### EntityDataMeta `STRUCT`
|
||||||
|
活动实体数据
|
||||||
|
```go
|
||||||
|
type EntityDataMeta[Data any] struct {
|
||||||
|
once sync.Once
|
||||||
|
Data Data
|
||||||
|
LastNewDay time.Time
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_UpcomingEventHandler"></span>
|
||||||
|
### UpcomingEventHandler `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type UpcomingEventHandler[ID generic.Basic] func(activityId ID)
|
||||||
|
```
|
||||||
|
<span id="struct_Options"></span>
|
||||||
|
### Options `STRUCT`
|
||||||
|
活动选项
|
||||||
|
```go
|
||||||
|
type Options struct {
|
||||||
|
Tl *times.StateLine[byte]
|
||||||
|
Loop time.Duration
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Options_WithUpcomingTime"></span>
|
||||||
|
|
||||||
|
#### func (*Options) WithUpcomingTime(t time.Time) *Options
|
||||||
|
> 设置活动预告时间
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Options_WithStartTime"></span>
|
||||||
|
|
||||||
|
#### func (*Options) WithStartTime(t time.Time) *Options
|
||||||
|
> 设置活动开始时间
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Options_WithEndTime"></span>
|
||||||
|
|
||||||
|
#### func (*Options) WithEndTime(t time.Time) *Options
|
||||||
|
> 设置活动结束时间
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Options_WithExtendedShowTime"></span>
|
||||||
|
|
||||||
|
#### func (*Options) WithExtendedShowTime(t time.Time) *Options
|
||||||
|
> 设置延长展示时间
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Options_WithLoop"></span>
|
||||||
|
|
||||||
|
#### func (*Options) WithLoop(interval time.Duration) *Options
|
||||||
|
> 设置活动循环,时间间隔小于等于 0 表示不循环
|
||||||
|
> - 当活动状态展示结束后,会根据该选项设置的时间间隔重新开始
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,146 @@
|
||||||
|
// Package activity 活动状态管理
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
stateClosed = byte(iota) // 已关闭
|
||||||
|
stateUpcoming // 即将开始
|
||||||
|
stateStarted // 已开始
|
||||||
|
stateEnded // 已结束
|
||||||
|
stateExtendedShowEnded // 延长展示结束
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
stateLine = []byte{stateClosed, stateUpcoming, stateStarted, stateEnded, stateExtendedShowEnded}
|
||||||
|
|
||||||
|
ticker *timer.Ticker
|
||||||
|
tickerOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ticker = timer.GetTicker(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTicker 设置自定义定时器,该方法必须在使用活动系统前调用,且只能调用一次
|
||||||
|
func SetTicker(size int, options ...timer.Option) {
|
||||||
|
tickerOnce.Do(func() {
|
||||||
|
if ticker != nil {
|
||||||
|
ticker.Release()
|
||||||
|
}
|
||||||
|
ticker = timer.GetTicker(size, options...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadGlobalData 加载所有活动全局数据
|
||||||
|
func LoadGlobalData(handler func(activityType, activityId, data any)) {
|
||||||
|
for _, f := range activityGlobalDataLoader {
|
||||||
|
f(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadEntityData 加载所有活动实体数据
|
||||||
|
func LoadEntityData(handler func(activityType, activityId, entityId, data any)) {
|
||||||
|
for _, f := range activityEntityDataLoader {
|
||||||
|
f(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOrRefreshActivity 加载或刷新活动
|
||||||
|
// - 通常在活动配置刷新时候将活动通过该方法注册或刷新
|
||||||
|
func LoadOrRefreshActivity[Type, ID generic.Basic](activityType Type, activityId ID, options ...*Options) error {
|
||||||
|
register, exist := activityRegister[activityType]
|
||||||
|
if !exist {
|
||||||
|
return fmt.Errorf("activity type %v not registered, activity %v registration failed", activityType, activityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity := register(activityId, initOptions(options...)).(*Activity[Type, ID])
|
||||||
|
if !activity.options.Tl.Check(true, stateLine...) {
|
||||||
|
return fmt.Errorf("activity %v state timeline is invalid", activityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateTrigger := map[byte]func(){
|
||||||
|
stateUpcoming: func() { OnUpcomingEvent(activity) },
|
||||||
|
stateStarted: func() { OnStartedEvent(activity) },
|
||||||
|
stateEnded: func() { OnEndedEvent(activity); OnExtendedShowStartedEvent(activity) },
|
||||||
|
stateExtendedShowEnded: func() { OnExtendedShowEndedEvent(activity) },
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range stateLine {
|
||||||
|
if activity.options.Tl.HasState(state) {
|
||||||
|
activity.options.Tl.AddTriggerToState(state, stateTrigger[state])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for next := state; next <= stateLine[len(stateLine)-1]; next++ {
|
||||||
|
if activity.options.Tl.HasState(next) {
|
||||||
|
activity.options.Tl.AddTriggerToState(next, stateTrigger[state])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.refresh()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activity 活动描述
|
||||||
|
type Activity[Type, ID generic.Basic] struct {
|
||||||
|
id ID // 活动 ID
|
||||||
|
t Type // 活动类型
|
||||||
|
options *Options // 活动选项
|
||||||
|
state byte // 活动状态
|
||||||
|
|
||||||
|
lazy bool // 是否懒加载
|
||||||
|
tickerKey string // 定时器 key
|
||||||
|
retention time.Duration // 活动数据保留时间
|
||||||
|
retentionKey string // 保留定时器 key
|
||||||
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
getLastNewDayTime func() time.Time // 获取上次新的一天的时间
|
||||||
|
setLastNewDayTime func(time.Time) // 设置上次新的一天的时间
|
||||||
|
clearData func() // 清理活动数据
|
||||||
|
initializeData func() // 初始化活动数据
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Activity[Type, ID]) refresh() {
|
||||||
|
slf.mutex.Lock()
|
||||||
|
defer slf.mutex.Unlock()
|
||||||
|
curr := time.Now()
|
||||||
|
if slf.state = slf.options.Tl.GetStateByTime(curr); slf.state == stateUpcoming || (slf.state == stateStarted && !slf.options.Tl.HasState(stateUpcoming)) {
|
||||||
|
ticker.StopTimer(slf.retentionKey)
|
||||||
|
slf.initializeData()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range slf.options.Tl.GetTriggerByState(slf.state) {
|
||||||
|
if f != nil {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next := slf.options.Tl.GetNextTimeByState(slf.state)
|
||||||
|
if !next.IsZero() && next.After(curr) {
|
||||||
|
ticker.After(slf.tickerKey, next.Sub(curr)+time.Millisecond*100, slf.refresh)
|
||||||
|
} else {
|
||||||
|
ticker.StopTimer(slf.tickerKey)
|
||||||
|
ticker.StopTimer(fmt.Sprintf("activity:new_day:%d:%v", reflect.ValueOf(slf.t).Kind(), slf.id))
|
||||||
|
if slf.options.Loop > 0 {
|
||||||
|
slf.options.Tl.Move(slf.options.Loop * 2)
|
||||||
|
ticker.After(slf.tickerKey, slf.options.Loop+time.Millisecond*100, slf.refresh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if slf.retention > 0 {
|
||||||
|
ticker.After(slf.tickerKey, slf.retention, func() {
|
||||||
|
ticker.StopTimer(slf.retentionKey)
|
||||||
|
slf.clearData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package activity_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/game/activity"
|
||||||
|
"github.com/kercylan98/minotaur/utils/times"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityData struct {
|
||||||
|
players []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerData struct {
|
||||||
|
info string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegTypeByGlobalData(t *testing.T) {
|
||||||
|
|
||||||
|
controller := activity.DefineGlobalDataActivity[int, int, *ActivityData](1).InitializeGlobalData(func(activityId int, data *activity.DataMeta[*ActivityData]) {
|
||||||
|
data.Data.players = []string{"1", "2", "3"}
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.RegUpcomingEvent(1, func(activityId int) {
|
||||||
|
t.Log(controller.GetGlobalData(activityId).players)
|
||||||
|
t.Log("即将开始")
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.RegStartedEvent(1, func(activityId int) {
|
||||||
|
t.Log("开始")
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.RegEndedEvent(1, func(activityId int) {
|
||||||
|
t.Log(controller.GetGlobalData(activityId).players)
|
||||||
|
t.Log("结束")
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.RegExtendedShowStartedEvent(1, func(activityId int) {
|
||||||
|
t.Log("延长展示开始")
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.RegExtendedShowEndedEvent(1, func(activityId int) {
|
||||||
|
t.Log("延长展示结束")
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.RegNewDayEvent(1, func(activityId int) {
|
||||||
|
t.Log("新的一天")
|
||||||
|
})
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if err := activity.LoadOrRefreshActivity(1, 1, activity.NewOptions().
|
||||||
|
WithUpcomingTime(now.Add(1*time.Second)).
|
||||||
|
WithStartTime(now.Add(2*times.Second)).
|
||||||
|
WithEndTime(now.Add(3*times.Second)).
|
||||||
|
WithExtendedShowTime(now.Add(4*times.Second)).
|
||||||
|
WithLoop(3*times.Second),
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(times.Week)
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type none byte
|
||||||
|
|
||||||
|
// DefineNoneDataActivity 声明无数据的活动类型
|
||||||
|
func DefineNoneDataActivity[Type, ID generic.Basic](activityType Type) NoneDataActivityController[Type, ID, *none, none, *none] {
|
||||||
|
return regController(&Controller[Type, ID, *none, none, *none]{
|
||||||
|
t: activityType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefineGlobalDataActivity 声明拥有全局数据的活动类型
|
||||||
|
func DefineGlobalDataActivity[Type, ID generic.Basic, Data any](activityType Type) GlobalDataActivityController[Type, ID, Data, none, *none] {
|
||||||
|
return regController(&Controller[Type, ID, Data, none, *none]{
|
||||||
|
t: activityType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefineEntityDataActivity 声明拥有实体数据的活动类型
|
||||||
|
func DefineEntityDataActivity[Type, ID, EntityID generic.Basic, EntityData any](activityType Type) EntityDataActivityController[Type, ID, *none, EntityID, EntityData] {
|
||||||
|
return regController(&Controller[Type, ID, *none, EntityID, EntityData]{
|
||||||
|
t: activityType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefineGlobalAndEntityDataActivity 声明拥有全局数据和实体数据的活动类型
|
||||||
|
func DefineGlobalAndEntityDataActivity[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any](activityType Type) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] {
|
||||||
|
return regController(&Controller[Type, ID, Data, EntityID, EntityData]{
|
||||||
|
t: activityType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controller 活动控制器
|
||||||
|
type Controller[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] struct {
|
||||||
|
t Type // 活动类型
|
||||||
|
activities map[ID]*Activity[Type, ID] // 活动列表
|
||||||
|
globalData map[ID]*DataMeta[Data] // 全局数据
|
||||||
|
entityData map[ID]map[EntityID]*EntityDataMeta[EntityData] // 实体数据
|
||||||
|
entityTof reflect.Type // 实体数据类型
|
||||||
|
globalInit func(activityId ID, data *DataMeta[Data]) // 全局数据初始化函数
|
||||||
|
entityInit func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData]) // 实体数据初始化函数
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalData 获取特定活动全局数据
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) GetGlobalData(activityId ID) Data {
|
||||||
|
slf.mutex.RLock()
|
||||||
|
defer slf.mutex.RUnlock()
|
||||||
|
global := slf.globalData[activityId]
|
||||||
|
if slf.globalInit != nil {
|
||||||
|
global.once.Do(func() {
|
||||||
|
slf.globalInit(activityId, global)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return global.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityData 获取特定活动实体数据
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) GetEntityData(activityId ID, entityId EntityID) EntityData {
|
||||||
|
slf.mutex.RLock()
|
||||||
|
defer slf.mutex.RUnlock()
|
||||||
|
entities, exist := slf.entityData[activityId]
|
||||||
|
if !exist {
|
||||||
|
entities = make(map[EntityID]*EntityDataMeta[EntityData])
|
||||||
|
slf.entityData[activityId] = entities
|
||||||
|
}
|
||||||
|
entity, exist := entities[entityId]
|
||||||
|
if !exist {
|
||||||
|
entity = &EntityDataMeta[EntityData]{
|
||||||
|
Data: reflect.New(slf.entityTof).Interface().(EntityData),
|
||||||
|
}
|
||||||
|
entities[entityId] = entity
|
||||||
|
}
|
||||||
|
if slf.entityInit != nil {
|
||||||
|
entity.once.Do(func() {
|
||||||
|
slf.entityInit(activityId, entityId, entity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return entity.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOpen 活动是否开启
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) IsOpen(activityId ID) bool {
|
||||||
|
slf.mutex.RLock()
|
||||||
|
activity, exist := slf.activities[activityId]
|
||||||
|
slf.mutex.RUnlock()
|
||||||
|
if !exist {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
activity.mutex.RLock()
|
||||||
|
defer activity.mutex.RUnlock()
|
||||||
|
return activity.state == stateStarted
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsShow 活动是否展示
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) IsShow(activityId ID) bool {
|
||||||
|
slf.mutex.RLock()
|
||||||
|
activity, exist := slf.activities[activityId]
|
||||||
|
slf.mutex.RUnlock()
|
||||||
|
if !exist {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
activity.mutex.RLock()
|
||||||
|
defer activity.mutex.RUnlock()
|
||||||
|
return activity.state == stateUpcoming || (activity.state == stateEnded && activity.options.Tl.HasState(stateExtendedShowEnded))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOpenOrShow 活动是否开启或展示
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) IsOpenOrShow(activityId ID) bool {
|
||||||
|
slf.mutex.RLock()
|
||||||
|
activity, exist := slf.activities[activityId]
|
||||||
|
slf.mutex.RUnlock()
|
||||||
|
if !exist {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.mutex.RLock()
|
||||||
|
defer activity.mutex.RUnlock()
|
||||||
|
return activity.state == stateStarted || activity.state == stateUpcoming || (activity.state == stateEnded && activity.options.Tl.HasState(stateExtendedShowEnded))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh 刷新活动
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) Refresh(activityId ID) {
|
||||||
|
slf.mutex.RLock()
|
||||||
|
activity, exist := slf.activities[activityId]
|
||||||
|
slf.mutex.RUnlock()
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
activity.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) InitializeNoneData(handler func(activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData] {
|
||||||
|
slf.globalInit = handler
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) InitializeGlobalData(handler func(activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData] {
|
||||||
|
slf.globalInit = handler
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) InitializeEntityData(handler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData] {
|
||||||
|
slf.entityInit = handler
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Controller[Type, ID, Data, EntityID, EntityData]) InitializeGlobalAndEntityData(handler func(activityId ID, data *DataMeta[Data]), entityHandler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] {
|
||||||
|
slf.globalInit = handler
|
||||||
|
slf.entityInit = entityHandler
|
||||||
|
return slf
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
|
||||||
|
type BasicActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
// IsOpen 活动是否开启
|
||||||
|
IsOpen(activityId ID) bool
|
||||||
|
// IsShow 活动是否展示
|
||||||
|
IsShow(activityId ID) bool
|
||||||
|
// IsOpenOrShow 活动是否开启或展示
|
||||||
|
IsOpenOrShow(activityId ID) bool
|
||||||
|
// Refresh 刷新活动
|
||||||
|
Refresh(activityId ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoneDataActivityController 无数据活动控制器
|
||||||
|
type NoneDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
// InitializeNoneData 初始化活动
|
||||||
|
// - 该函数提供了一个操作活动数据的入口,可以在该函数中对传入的活动数据进行初始化
|
||||||
|
//
|
||||||
|
// 对于无数据活动,该函数的意义在于,可以在该函数中对活动进行初始化,比如设置活动的状态等,虽然为无数据活动,但是例如活动本身携带的状态数据也是需要加载的
|
||||||
|
InitializeNoneData(handler func(activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalDataActivityController 全局数据活动控制器
|
||||||
|
type GlobalDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
// GetGlobalData 获取全局数据
|
||||||
|
GetGlobalData(activityId ID) Data
|
||||||
|
// InitializeGlobalData 初始化活动
|
||||||
|
// - 该函数提供了一个操作活动数据的入口,可以在该函数中对传入的活动数据进行初始化
|
||||||
|
InitializeGlobalData(handler func(activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntityDataActivityController 实体数据活动控制器
|
||||||
|
type EntityDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
// GetEntityData 获取实体数据
|
||||||
|
GetEntityData(activityId ID, entityId EntityID) EntityData
|
||||||
|
// InitializeEntityData 初始化活动
|
||||||
|
// - 该函数提供了一个操作活动数据的入口,可以在该函数中对传入的活动数据进行初始化
|
||||||
|
InitializeEntityData(handler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalAndEntityDataActivityController 全局数据和实体数据活动控制器
|
||||||
|
type GlobalAndEntityDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface {
|
||||||
|
BasicActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
// GetGlobalData 获取全局数据
|
||||||
|
GetGlobalData(activityId ID) Data
|
||||||
|
// GetEntityData 获取实体数据
|
||||||
|
GetEntityData(activityId ID, entityId EntityID) EntityData
|
||||||
|
// InitializeGlobalAndEntityData 初始化活动
|
||||||
|
// - 该函数提供了一个操作活动数据的入口,可以在该函数中对传入的活动数据进行初始化
|
||||||
|
InitializeGlobalAndEntityData(handler func(activityId ID, data *DataMeta[Data]), entityHandler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData]
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/collection"
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"github.com/kercylan98/minotaur/utils/times"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
activityRegister map[any]func(activityId any, options *Options) (activity any) // type -> register (控制活动注册到特定类型控制器的注册机)
|
||||||
|
activityGlobalDataLoader []func(handler func(activityType, activityId, data any)) // 全局数据加载器
|
||||||
|
activityEntityDataLoader []func(handler func(activityType, activityId, entityId, data any)) // 实体数据加载器
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
activityRegister = make(map[any]func(activityId any, options *Options) (activity any))
|
||||||
|
}
|
||||||
|
|
||||||
|
// regController 注册活动类型控制器
|
||||||
|
func regController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any](controller *Controller[Type, ID, Data, EntityID, EntityData]) *Controller[Type, ID, Data, EntityID, EntityData] {
|
||||||
|
var entityZero EntityData
|
||||||
|
controller.activities = make(map[ID]*Activity[Type, ID])
|
||||||
|
controller.globalData = make(map[ID]*DataMeta[Data])
|
||||||
|
controller.entityTof = reflect.TypeOf(entityZero)
|
||||||
|
if controller.entityTof.Kind() == reflect.Pointer {
|
||||||
|
controller.entityTof = controller.entityTof.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反射类型
|
||||||
|
var (
|
||||||
|
zero Data
|
||||||
|
tof = reflect.TypeOf(zero)
|
||||||
|
)
|
||||||
|
if tof.Kind() == reflect.Pointer {
|
||||||
|
tof = tof.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 活动注册机(注册机内不加载活动数据,仅定义基本活动信息)
|
||||||
|
activityRegister[controller.t] = func(aid any, options *Options) any {
|
||||||
|
activityId := aid.(ID)
|
||||||
|
controller.mutex.Lock()
|
||||||
|
activity, exist := controller.activities[activityId]
|
||||||
|
if !exist {
|
||||||
|
activity = &Activity[Type, ID]{
|
||||||
|
t: controller.t,
|
||||||
|
id: activityId,
|
||||||
|
options: options,
|
||||||
|
tickerKey: fmt.Sprintf("activity:%d:%v", reflect.ValueOf(controller.t).Kind(), activityId),
|
||||||
|
getLastNewDayTime: func() time.Time {
|
||||||
|
return controller.globalData[activityId].LastNewDay
|
||||||
|
},
|
||||||
|
setLastNewDayTime: func(t time.Time) {
|
||||||
|
controller.globalData[activityId].LastNewDay = t
|
||||||
|
},
|
||||||
|
clearData: func() {
|
||||||
|
controller.mutex.Lock()
|
||||||
|
defer controller.mutex.Unlock()
|
||||||
|
delete(controller.globalData, activityId)
|
||||||
|
delete(controller.entityData, activityId)
|
||||||
|
},
|
||||||
|
initializeData: func() {
|
||||||
|
controller.mutex.Lock()
|
||||||
|
defer controller.mutex.Unlock()
|
||||||
|
controller.globalData[activityId] = &DataMeta[Data]{
|
||||||
|
Data: reflect.New(tof).Interface().(Data),
|
||||||
|
}
|
||||||
|
if controller.entityData == nil {
|
||||||
|
controller.entityData = make(map[ID]map[EntityID]*EntityDataMeta[EntityData])
|
||||||
|
}
|
||||||
|
controller.entityData[activityId] = make(map[EntityID]*EntityDataMeta[EntityData])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if activity.options == nil {
|
||||||
|
activity.options = NewOptions()
|
||||||
|
}
|
||||||
|
if activity.options.Tl == nil || activity.options.Tl.GetStateCount() == 0 {
|
||||||
|
activity.options.Tl = times.NewStateLine[byte](stateClosed)
|
||||||
|
}
|
||||||
|
controller.activities[activityId] = activity
|
||||||
|
}
|
||||||
|
controller.mutex.Unlock()
|
||||||
|
|
||||||
|
// 全局数据加载器
|
||||||
|
activityGlobalDataLoader = append(activityGlobalDataLoader, func(handler func(activityType any, activityId any, data any)) {
|
||||||
|
controller.mutex.RLock()
|
||||||
|
data := controller.globalData[activityId]
|
||||||
|
controller.mutex.RUnlock()
|
||||||
|
handler(controller.t, activityId, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 实体数据加载器
|
||||||
|
activityEntityDataLoader = append(activityEntityDataLoader, func(handler func(activityType any, activityId any, entityId any, data any)) {
|
||||||
|
controller.mutex.RLock()
|
||||||
|
entities := collection.CloneMap(controller.entityData[activityId])
|
||||||
|
controller.mutex.RUnlock()
|
||||||
|
for entityId, data := range entities {
|
||||||
|
handler(controller.t, activityId, entityId, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return activity
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataMeta 全局活动数据
|
||||||
|
type DataMeta[Data any] struct {
|
||||||
|
once sync.Once
|
||||||
|
Data Data `json:"data,omitempty"` // 活动数据
|
||||||
|
LastNewDay time.Time `json:"last_new_day,omitempty"` // 上次跨天时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntityDataMeta 活动实体数据
|
||||||
|
type EntityDataMeta[Data any] struct {
|
||||||
|
once sync.Once
|
||||||
|
Data Data `json:"data,omitempty"` // 活动数据
|
||||||
|
LastNewDay time.Time `json:"last_new_day,omitempty"` // 上次跨天时间
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/collection"
|
||||||
|
"github.com/kercylan98/minotaur/utils/collection/listings"
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"github.com/kercylan98/minotaur/utils/log"
|
||||||
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
|
"github.com/kercylan98/minotaur/utils/times"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UpcomingEventHandler[ID generic.Basic] func(activityId ID) // 即将开始的活动事件处理器
|
||||||
|
StartedEventHandler[ID generic.Basic] func(activityId ID) // 活动开始事件处理器
|
||||||
|
EndedEventHandler[ID generic.Basic] func(activityId ID) // 活动结束事件处理器
|
||||||
|
ExtendedShowStartedEventHandler[ID generic.Basic] func(activityId ID) // 活动结束后延长展示开始事件处理器
|
||||||
|
ExtendedShowEndedEventHandler[ID generic.Basic] func(activityId ID) // 活动结束后延长展示结束事件处理器
|
||||||
|
NewDayEventHandler[ID generic.Basic] func(activityId ID) // 新的一天事件处理器
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
upcomingEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 即将开始的活动事件处理器
|
||||||
|
startedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动开始事件处理器
|
||||||
|
endedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束事件处理器
|
||||||
|
extShowStartedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束后延长展示开始事件处理器
|
||||||
|
extShowEndedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束后延长展示结束事件处理器
|
||||||
|
newDayEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 新的一天事件处理器
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
upcomingEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||||
|
startedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||||
|
endedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||||
|
extShowStartedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||||
|
extShowEndedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||||
|
newDayEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegUpcomingEvent 注册即将开始的活动事件处理器
|
||||||
|
func RegUpcomingEvent[Type, ID generic.Basic](activityType Type, handler UpcomingEventHandler[ID], priority ...int) {
|
||||||
|
handlers, exist := upcomingEventHandlers[activityType]
|
||||||
|
if !exist {
|
||||||
|
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||||
|
upcomingEventHandlers[activityType] = handlers
|
||||||
|
}
|
||||||
|
handlers.Append(func(activityId any) {
|
||||||
|
if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(activityId.(ID))
|
||||||
|
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnUpcomingEvent 即将开始的活动事件
|
||||||
|
func OnUpcomingEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
|
||||||
|
handlers, exist := upcomingEventHandlers[activity.t]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handlers.RangeValue(func(index int, value func(activityId any)) bool {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("OnUpcomingEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
value(activity.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegStartedEvent 注册活动开始事件处理器
|
||||||
|
func RegStartedEvent[Type, ID generic.Basic](activityType Type, handler StartedEventHandler[ID], priority ...int) {
|
||||||
|
handlers, exist := startedEventHandlers[activityType]
|
||||||
|
if !exist {
|
||||||
|
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||||
|
startedEventHandlers[activityType] = handlers
|
||||||
|
}
|
||||||
|
handlers.Append(func(activityId any) {
|
||||||
|
if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(activityId.(ID))
|
||||||
|
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnStartedEvent 活动开始事件
|
||||||
|
func OnStartedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
|
||||||
|
handlers, exist := startedEventHandlers[activity.t]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handlers.RangeValue(func(index int, value func(activityId any)) bool {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("OnStartedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
value(activity.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if !times.IsSameDay(now, activity.getLastNewDayTime()) {
|
||||||
|
OnNewDayEvent(activity)
|
||||||
|
}
|
||||||
|
ticker.Loop(fmt.Sprintf("activity:new_day:%d:%v", reflect.ValueOf(activity.t).Kind(), activity.id), times.GetNextDayInterval(now), times.Day, timer.Forever, func() {
|
||||||
|
OnNewDayEvent(activity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegEndedEvent 注册活动结束事件处理器
|
||||||
|
func RegEndedEvent[Type, ID generic.Basic](activityType Type, handler EndedEventHandler[ID], priority ...int) {
|
||||||
|
handlers, exist := endedEventHandlers[activityType]
|
||||||
|
if !exist {
|
||||||
|
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||||
|
endedEventHandlers[activityType] = handlers
|
||||||
|
}
|
||||||
|
handlers.Append(func(activityId any) {
|
||||||
|
if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(activityId.(ID))
|
||||||
|
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnEndedEvent 活动结束事件
|
||||||
|
func OnEndedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
|
||||||
|
handlers, exist := endedEventHandlers[activity.t]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handlers.RangeValue(func(index int, value func(activityId any)) bool {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("OnEndedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
value(activity.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegExtendedShowStartedEvent 注册活动结束后延长展示开始事件处理器
|
||||||
|
func RegExtendedShowStartedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int) {
|
||||||
|
handlers, exist := extShowStartedEventHandlers[activityType]
|
||||||
|
if !exist {
|
||||||
|
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||||
|
extShowStartedEventHandlers[activityType] = handlers
|
||||||
|
}
|
||||||
|
handlers.Append(func(activityId any) {
|
||||||
|
if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(activityId.(ID))
|
||||||
|
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnExtendedShowStartedEvent 活动结束后延长展示开始事件
|
||||||
|
func OnExtendedShowStartedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
|
||||||
|
handlers, exist := extShowStartedEventHandlers[activity.t]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handlers.RangeValue(func(index int, value func(activityId any)) bool {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("OnExtendedShowStartedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
value(activity.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegExtendedShowEndedEvent 注册活动结束后延长展示结束事件处理器
|
||||||
|
func RegExtendedShowEndedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int) {
|
||||||
|
handlers, exist := extShowEndedEventHandlers[activityType]
|
||||||
|
if !exist {
|
||||||
|
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||||
|
extShowEndedEventHandlers[activityType] = handlers
|
||||||
|
}
|
||||||
|
handlers.Append(func(activityId any) {
|
||||||
|
if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(activityId.(ID))
|
||||||
|
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnExtendedShowEndedEvent 活动结束后延长展示结束事件
|
||||||
|
func OnExtendedShowEndedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
|
||||||
|
handlers, exist := extShowEndedEventHandlers[activity.t]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handlers.RangeValue(func(index int, value func(activityId any)) bool {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("OnExtendedShowEndedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
value(activity.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegNewDayEvent 注册新的一天事件处理器
|
||||||
|
func RegNewDayEvent[Type, ID generic.Basic](activityType Type, handler NewDayEventHandler[ID], priority ...int) {
|
||||||
|
handlers, exist := newDayEventHandlers[activityType]
|
||||||
|
if !exist {
|
||||||
|
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||||
|
newDayEventHandlers[activityType] = handlers
|
||||||
|
}
|
||||||
|
handlers.Append(func(activityId any) {
|
||||||
|
if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(activityId.(ID))
|
||||||
|
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnNewDayEvent 新的一天事件
|
||||||
|
func OnNewDayEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
|
||||||
|
handlers, exist := newDayEventHandlers[activity.t]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handlers.RangeValue(func(index int, value func(activityId any)) bool {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("OnNewDayEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
value(activity.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Main
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Activities
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,24 @@
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/game/activity"
|
||||||
|
"github.com/kercylan98/minotaur/game/activity/internal/example/types"
|
||||||
|
"github.com/kercylan98/minotaur/utils/super"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DemoActivity = activity.DefineEntityDataActivity[int, int, string, *types.DemoActivityData](1).InitializeEntityData(func(activityId int, entityId string, data *activity.EntityDataMeta[*types.DemoActivityData]) {
|
||||||
|
// 模拟数据库加载
|
||||||
|
_ = super.UnmarshalJSON([]byte(`{"last_new_day": "2021-01-01 00:00:00", "data": {"login_num": 3}}`), data)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// 模拟配置加载活动
|
||||||
|
if err := activity.LoadOrRefreshActivity(1, 1, activity.NewOptions().
|
||||||
|
WithStartTime(time.Now().Add(time.Second*3)),
|
||||||
|
); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Demoactivity
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,15 @@
|
||||||
|
package demoactivity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/game/activity"
|
||||||
|
"github.com/kercylan98/minotaur/game/activity/internal/example/activities"
|
||||||
|
"github.com/kercylan98/minotaur/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
activity.RegStartedEvent(1, onActivityStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
func onActivityStart(id int) {
|
||||||
|
log.Info("activity start", log.Int("id", id), log.Any("entity", activities.DemoActivity.GetEntityData(id, "demo_entity")))
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/kercylan98/minotaur/game/activity/internal/example/activities/demoactivity"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Types
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[DemoActivityData](#struct_DemoActivityData)|暂无描述...
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
<span id="struct_DemoActivityData"></span>
|
||||||
|
### DemoActivityData `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type DemoActivityData struct {
|
||||||
|
LoginNum int
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,5 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type DemoActivityData struct {
|
||||||
|
LoginNum int `json:"login_num"` // 登录次数
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/times"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewOptions 创建活动选项
|
||||||
|
func NewOptions() *Options {
|
||||||
|
return new(Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initOptions 初始化活动选项
|
||||||
|
func initOptions(opts ...*Options) *Options {
|
||||||
|
var opt *Options
|
||||||
|
if len(opts) > 0 {
|
||||||
|
opt = opts[0]
|
||||||
|
}
|
||||||
|
if opt == nil {
|
||||||
|
opt = NewOptions()
|
||||||
|
}
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options 活动选项
|
||||||
|
type Options struct {
|
||||||
|
Tl *times.StateLine[byte] // 活动时间线
|
||||||
|
Loop time.Duration // 活动循环,时间间隔小于等于 0 表示不循环
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUpcomingTime 设置活动预告时间
|
||||||
|
func (slf *Options) WithUpcomingTime(t time.Time) *Options {
|
||||||
|
if slf.Tl == nil {
|
||||||
|
slf.Tl = times.NewStateLine[byte](stateClosed)
|
||||||
|
}
|
||||||
|
slf.Tl.AddState(stateUpcoming, t)
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStartTime 设置活动开始时间
|
||||||
|
func (slf *Options) WithStartTime(t time.Time) *Options {
|
||||||
|
if slf.Tl == nil {
|
||||||
|
slf.Tl = times.NewStateLine[byte](stateClosed)
|
||||||
|
}
|
||||||
|
slf.Tl.AddState(stateStarted, t)
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEndTime 设置活动结束时间
|
||||||
|
func (slf *Options) WithEndTime(t time.Time) *Options {
|
||||||
|
if slf.Tl == nil {
|
||||||
|
slf.Tl = times.NewStateLine[byte](stateClosed)
|
||||||
|
}
|
||||||
|
slf.Tl.AddState(stateEnded, t)
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExtendedShowTime 设置延长展示时间
|
||||||
|
func (slf *Options) WithExtendedShowTime(t time.Time) *Options {
|
||||||
|
if slf.Tl == nil {
|
||||||
|
slf.Tl = times.NewStateLine[byte](stateClosed)
|
||||||
|
}
|
||||||
|
slf.Tl.AddState(stateExtendedShowEnded, t)
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLoop 设置活动循环,时间间隔小于等于 0 表示不循环
|
||||||
|
// - 当活动状态展示结束后,会根据该选项设置的时间间隔重新开始
|
||||||
|
func (slf *Options) WithLoop(interval time.Duration) *Options {
|
||||||
|
slf.Loop = interval
|
||||||
|
return slf
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package game 目录下包含了各类通用的游戏玩法性内容,其中该目录主要为基础性内容,具体目录将对应不同的游戏功能性内容。
|
||||||
|
package game
|
|
@ -0,0 +1,229 @@
|
||||||
|
# Fight
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 包级函数定义
|
||||||
|
|
||||||
|
|函数名称|描述
|
||||||
|
|:--|:--
|
||||||
|
|[NewTurnBased](#NewTurnBased)|创建一个新的回合制
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[TurnBased](#struct_TurnBased)|回合制
|
||||||
|
|`INTERFACE`|[TurnBasedControllerInfo](#struct_TurnBasedControllerInfo)|暂无描述...
|
||||||
|
|`INTERFACE`|[TurnBasedControllerAction](#struct_TurnBasedControllerAction)|暂无描述...
|
||||||
|
|`STRUCT`|[TurnBasedController](#struct_TurnBasedController)|回合制控制器
|
||||||
|
|`STRUCT`|[TurnBasedEntitySwitchEventHandler](#struct_TurnBasedEntitySwitchEventHandler)|暂无描述...
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
#### func NewTurnBased\[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]\](calcNextTurnDuration func ( Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity]
|
||||||
|
<span id="NewTurnBased"></span>
|
||||||
|
> 创建一个新的回合制
|
||||||
|
> - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBased"></span>
|
||||||
|
### TurnBased `STRUCT`
|
||||||
|
回合制
|
||||||
|
```go
|
||||||
|
type TurnBased[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||||
|
*turnBasedEvents[CampID, EntityID, Camp, Entity]
|
||||||
|
controller *TurnBasedController[CampID, EntityID, Camp, Entity]
|
||||||
|
ticker *time.Ticker
|
||||||
|
actionWaitTicker *time.Ticker
|
||||||
|
actioning bool
|
||||||
|
actionMutex sync.RWMutex
|
||||||
|
entities []Entity
|
||||||
|
campRel map[EntityID]Camp
|
||||||
|
calcNextTurnDuration func(Camp, Entity) time.Duration
|
||||||
|
actionTimeoutHandler func(Camp, Entity) time.Duration
|
||||||
|
signal chan signal
|
||||||
|
round int
|
||||||
|
currCamp Camp
|
||||||
|
currEntity Entity
|
||||||
|
currActionTimeout time.Duration
|
||||||
|
currStart time.Time
|
||||||
|
closeMutex sync.RWMutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_TurnBased_Close"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBased) Close()
|
||||||
|
> 关闭回合制
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBased_AddCamp"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBased) AddCamp(camp Camp, entity Entity, entities ...Entity)
|
||||||
|
> 添加阵营
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBased_SetActionTimeout"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBased) SetActionTimeout(actionTimeoutHandler func ( Camp, Entity) time.Duration)
|
||||||
|
> 设置行动超时时间处理函数
|
||||||
|
> - 默认情况下行动超时时间函数将始终返回 0
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBased_Run"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBased) Run()
|
||||||
|
> 运行
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>查看 / 收起单元测试</summary>
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
func TestTurnBased_Run(t *testing.T) {
|
||||||
|
tbi := fight.NewTurnBased[string, string, *Camp, *Entity](func(camp *Camp, entity *Entity) time.Duration {
|
||||||
|
return time.Duration(float64(time.Second) / entity.speed)
|
||||||
|
})
|
||||||
|
tbi.SetActionTimeout(func(camp *Camp, entity *Entity) time.Duration {
|
||||||
|
return time.Second * 5
|
||||||
|
})
|
||||||
|
tbi.RegTurnBasedEntityActionTimeoutEvent(func(controller fight.TurnBasedControllerInfo[string, string, *Camp, *Entity]) {
|
||||||
|
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "超时")
|
||||||
|
})
|
||||||
|
tbi.RegTurnBasedRoundChangeEvent(func(controller fight.TurnBasedControllerInfo[string, string, *Camp, *Entity]) {
|
||||||
|
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "回合切换")
|
||||||
|
})
|
||||||
|
tbi.RegTurnBasedEntitySwitchEvent(func(controller fight.TurnBasedControllerAction[string, string, *Camp, *Entity]) {
|
||||||
|
switch controller.GetEntity().GetId() {
|
||||||
|
case "1":
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
controller.Finish()
|
||||||
|
}()
|
||||||
|
case "2":
|
||||||
|
controller.Refresh(time.Second)
|
||||||
|
case "4":
|
||||||
|
controller.Stop()
|
||||||
|
}
|
||||||
|
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "开始行动")
|
||||||
|
})
|
||||||
|
tbi.AddCamp(&Camp{id: "1"}, &Entity{id: "1", speed: 1}, &Entity{id: "2", speed: 1})
|
||||||
|
tbi.AddCamp(&Camp{id: "2"}, &Entity{id: "3", speed: 1}, &Entity{id: "4", speed: 1})
|
||||||
|
tbi.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedControllerInfo"></span>
|
||||||
|
### TurnBasedControllerInfo `INTERFACE`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TurnBasedControllerInfo[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface {
|
||||||
|
GetRound() int
|
||||||
|
GetCamp() Camp
|
||||||
|
GetEntity() Entity
|
||||||
|
GetActionTimeoutDuration() time.Duration
|
||||||
|
GetActionStartTime() time.Time
|
||||||
|
GetActionEndTime() time.Time
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_TurnBasedControllerAction"></span>
|
||||||
|
### TurnBasedControllerAction `INTERFACE`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TurnBasedControllerAction[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface {
|
||||||
|
TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]
|
||||||
|
Finish()
|
||||||
|
Refresh(duration time.Duration) time.Time
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_TurnBasedController"></span>
|
||||||
|
### TurnBasedController `STRUCT`
|
||||||
|
回合制控制器
|
||||||
|
```go
|
||||||
|
type TurnBasedController[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||||
|
tb *TurnBased[CampID, EntityID, Camp, Entity]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_TurnBasedController_GetRound"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) GetRound() int
|
||||||
|
> 获取当前回合数
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_GetCamp"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) GetCamp() Camp
|
||||||
|
> 获取当前操作阵营
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_GetEntity"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) GetEntity() Entity
|
||||||
|
> 获取当前操作实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_GetActionTimeoutDuration"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) GetActionTimeoutDuration() time.Duration
|
||||||
|
> 获取当前行动超时时长
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_GetActionStartTime"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) GetActionStartTime() time.Time
|
||||||
|
> 获取当前行动开始时间
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_GetActionEndTime"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) GetActionEndTime() time.Time
|
||||||
|
> 获取当前行动结束时间
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_Finish"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) Finish()
|
||||||
|
> 结束当前操作,将立即切换到下一个操作实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_Stop"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) Stop()
|
||||||
|
> 在当前回合执行完毕后停止回合进程
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedController_Refresh"></span>
|
||||||
|
|
||||||
|
#### func (*TurnBasedController) Refresh(duration time.Duration) time.Time
|
||||||
|
> 刷新当前操作实体的行动超时时间
|
||||||
|
> - 当不在行动阶段时,将返回 time.Time 零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_TurnBasedEntitySwitchEventHandler"></span>
|
||||||
|
### TurnBasedEntitySwitchEventHandler `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TurnBasedEntitySwitchEventHandler[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerAction[CampID, EntityID, Camp, Entity])
|
||||||
|
```
|
|
@ -0,0 +1,197 @@
|
||||||
|
package fight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
signalFinish = 1 + iota // 操作结束信号
|
||||||
|
signalRefresh // 刷新操作超时时间信号
|
||||||
|
)
|
||||||
|
|
||||||
|
type signal struct {
|
||||||
|
sign byte
|
||||||
|
data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTurnBased 创建一个新的回合制
|
||||||
|
// - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间
|
||||||
|
func NewTurnBased[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]](calcNextTurnDuration func(Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] {
|
||||||
|
tb := &TurnBased[CampID, EntityID, Camp, Entity]{
|
||||||
|
turnBasedEvents: &turnBasedEvents[CampID, EntityID, Camp, Entity]{},
|
||||||
|
campRel: make(map[EntityID]Camp),
|
||||||
|
calcNextTurnDuration: calcNextTurnDuration,
|
||||||
|
actionTimeoutHandler: func(camp Camp, entity Entity) time.Duration {
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tb.controller = &TurnBasedController[CampID, EntityID, Camp, Entity]{tb: tb}
|
||||||
|
return tb
|
||||||
|
}
|
||||||
|
|
||||||
|
// TurnBased 回合制
|
||||||
|
type TurnBased[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||||
|
*turnBasedEvents[CampID, EntityID, Camp, Entity]
|
||||||
|
controller *TurnBasedController[CampID, EntityID, Camp, Entity] // 控制器
|
||||||
|
ticker *time.Ticker // 计时器
|
||||||
|
actionWaitTicker *time.Ticker // 行动等待计时器
|
||||||
|
actioning bool // 是否正在行动
|
||||||
|
actionMutex sync.RWMutex // 行动锁
|
||||||
|
entities []Entity // 所有阵容实体顺序
|
||||||
|
campRel map[EntityID]Camp // 实体与阵营的关系
|
||||||
|
calcNextTurnDuration func(Camp, Entity) time.Duration // 下一次行动时间间隔
|
||||||
|
actionTimeoutHandler func(Camp, Entity) time.Duration // 行动超时时间
|
||||||
|
|
||||||
|
signal chan signal // 信号
|
||||||
|
round int // 当前回合数
|
||||||
|
currCamp Camp // 当前操作阵营
|
||||||
|
currEntity Entity // 当前操作实体
|
||||||
|
currActionTimeout time.Duration // 当前行动超时时间
|
||||||
|
currStart time.Time // 当前回合开始时间
|
||||||
|
|
||||||
|
closeMutex sync.RWMutex // 关闭锁
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭回合制
|
||||||
|
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) Close() {
|
||||||
|
slf.closeMutex.Lock()
|
||||||
|
defer slf.closeMutex.Unlock()
|
||||||
|
slf.closed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCamp 添加阵营
|
||||||
|
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) AddCamp(camp Camp, entity Entity, entities ...Entity) {
|
||||||
|
for _, e := range append([]Entity{entity}, entities...) {
|
||||||
|
slf.entities = append(slf.entities, e)
|
||||||
|
slf.campRel[e.GetId()] = camp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetActionTimeout 设置行动超时时间处理函数
|
||||||
|
// - 默认情况下行动超时时间函数将始终返回 0
|
||||||
|
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) SetActionTimeout(actionTimeoutHandler func(Camp, Entity) time.Duration) {
|
||||||
|
if actionTimeoutHandler == nil {
|
||||||
|
panic("actionTimeoutHandler can not be nil")
|
||||||
|
}
|
||||||
|
slf.actionTimeoutHandler = actionTimeoutHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run 运行
|
||||||
|
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) Run() {
|
||||||
|
slf.round = 1
|
||||||
|
slf.signal = make(chan signal, 1)
|
||||||
|
var actionDuration = make(map[EntityID]time.Duration)
|
||||||
|
var actionSubmit = func() {
|
||||||
|
slf.actionMutex.Lock()
|
||||||
|
slf.actioning = false
|
||||||
|
if slf.actionWaitTicker != nil {
|
||||||
|
slf.actionWaitTicker.Stop()
|
||||||
|
}
|
||||||
|
slf.actionMutex.Unlock()
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
slf.closeMutex.RLock()
|
||||||
|
if slf.closed {
|
||||||
|
slf.closeMutex.RUnlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
slf.closeMutex.RUnlock()
|
||||||
|
|
||||||
|
var minDuration *time.Duration
|
||||||
|
var delay time.Duration
|
||||||
|
for _, entity := range slf.entities {
|
||||||
|
camp := slf.campRel[entity.GetId()]
|
||||||
|
next := slf.calcNextTurnDuration(camp, entity)
|
||||||
|
accumulate := next + actionDuration[entity.GetId()]
|
||||||
|
if minDuration == nil || accumulate < *minDuration {
|
||||||
|
minDuration = &accumulate
|
||||||
|
slf.currEntity = entity
|
||||||
|
slf.currCamp = camp
|
||||||
|
delay = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *minDuration == 0 {
|
||||||
|
*minDuration = 1 // 防止永远是第一对象行动
|
||||||
|
}
|
||||||
|
actionDuration[slf.currEntity.GetId()] = *minDuration
|
||||||
|
if len(actionDuration) == len(slf.entities) {
|
||||||
|
for key := range actionDuration {
|
||||||
|
delete(actionDuration, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay > 0 {
|
||||||
|
if slf.ticker == nil {
|
||||||
|
slf.ticker = time.NewTicker(delay)
|
||||||
|
} else {
|
||||||
|
slf.ticker.Reset(delay)
|
||||||
|
}
|
||||||
|
<-slf.ticker.C
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进入回合操作阶段
|
||||||
|
slf.currActionTimeout = slf.actionTimeoutHandler(slf.currCamp, slf.currEntity)
|
||||||
|
slf.currStart = time.Now()
|
||||||
|
slf.actionMutex.Lock()
|
||||||
|
slf.actioning = true
|
||||||
|
slf.actionMutex.Unlock()
|
||||||
|
slf.OnTurnBasedEntitySwitchEvent(slf.controller)
|
||||||
|
if slf.actionWaitTicker == nil {
|
||||||
|
slf.actionWaitTicker = time.NewTicker(slf.currActionTimeout)
|
||||||
|
} else {
|
||||||
|
slf.actionWaitTicker.Reset(slf.currActionTimeout)
|
||||||
|
}
|
||||||
|
breakListen:
|
||||||
|
for {
|
||||||
|
wait:
|
||||||
|
select {
|
||||||
|
case <-slf.actionWaitTicker.C:
|
||||||
|
actionSubmit()
|
||||||
|
slf.OnTurnBasedEntityActionTimeoutEvent(slf.controller)
|
||||||
|
break breakListen
|
||||||
|
case sign := <-slf.signal:
|
||||||
|
switch sign.sign {
|
||||||
|
case signalFinish:
|
||||||
|
actionSubmit()
|
||||||
|
slf.OnTurnBasedEntityActionFinishEvent(slf.controller)
|
||||||
|
break breakListen
|
||||||
|
case signalRefresh:
|
||||||
|
slf.actionWaitTicker.Reset(sign.data.(time.Duration))
|
||||||
|
goto wait
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slf.OnTurnBasedEntityActionSubmitEvent(slf.controller)
|
||||||
|
if len(actionDuration) == 0 {
|
||||||
|
slf.round++
|
||||||
|
}
|
||||||
|
|
||||||
|
slf.closeMutex.Lock()
|
||||||
|
if slf.closed {
|
||||||
|
if len(actionDuration) == 0 {
|
||||||
|
slf.round--
|
||||||
|
}
|
||||||
|
if slf.ticker != nil {
|
||||||
|
slf.ticker.Stop()
|
||||||
|
slf.ticker = nil
|
||||||
|
}
|
||||||
|
if slf.actionWaitTicker != nil {
|
||||||
|
slf.actionWaitTicker.Stop()
|
||||||
|
slf.actionWaitTicker = nil
|
||||||
|
}
|
||||||
|
if slf.signal != nil {
|
||||||
|
close(slf.signal)
|
||||||
|
slf.signal = nil
|
||||||
|
}
|
||||||
|
slf.closeMutex.Unlock()
|
||||||
|
break
|
||||||
|
} else if len(actionDuration) == 0 {
|
||||||
|
slf.OnTurnBasedRoundChangeEvent(slf.controller)
|
||||||
|
}
|
||||||
|
slf.closeMutex.Unlock()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package fight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TurnBasedControllerInfo[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface {
|
||||||
|
// GetRound 获取当前回合数
|
||||||
|
GetRound() int
|
||||||
|
// GetCamp 获取当前操作阵营
|
||||||
|
GetCamp() Camp
|
||||||
|
// GetEntity 获取当前操作实体
|
||||||
|
GetEntity() Entity
|
||||||
|
// GetActionTimeoutDuration 获取当前行动超时时长
|
||||||
|
GetActionTimeoutDuration() time.Duration
|
||||||
|
// GetActionStartTime 获取当前行动开始时间
|
||||||
|
GetActionStartTime() time.Time
|
||||||
|
// GetActionEndTime 获取当前行动结束时间
|
||||||
|
GetActionEndTime() time.Time
|
||||||
|
// Stop 在当前回合执行完毕后停止回合进程
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type TurnBasedControllerAction[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface {
|
||||||
|
TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]
|
||||||
|
// Finish 结束当前操作,将立即切换到下一个操作实体
|
||||||
|
Finish()
|
||||||
|
// Refresh 刷新当前操作实体的行动超时时间并返回新的行动超时时间
|
||||||
|
Refresh(duration time.Duration) time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// TurnBasedController 回合制控制器
|
||||||
|
type TurnBasedController[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||||
|
tb *TurnBased[CampID, EntityID, Camp, Entity]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRound 获取当前回合数
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetRound() int {
|
||||||
|
return slf.tb.round
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCamp 获取当前操作阵营
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetCamp() Camp {
|
||||||
|
return slf.tb.currCamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntity 获取当前操作实体
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetEntity() Entity {
|
||||||
|
return slf.tb.currEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActionTimeoutDuration 获取当前行动超时时长
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetActionTimeoutDuration() time.Duration {
|
||||||
|
return slf.tb.currActionTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActionStartTime 获取当前行动开始时间
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetActionStartTime() time.Time {
|
||||||
|
return slf.tb.currStart
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActionEndTime 获取当前行动结束时间
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetActionEndTime() time.Time {
|
||||||
|
return slf.tb.currStart.Add(slf.tb.currActionTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish 结束当前操作,将立即切换到下一个操作实体
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) Finish() {
|
||||||
|
slf.tb.actionMutex.Lock()
|
||||||
|
defer slf.tb.actionMutex.Unlock()
|
||||||
|
if slf.tb.actioning {
|
||||||
|
slf.tb.actioning = false
|
||||||
|
slf.tb.signal <- signal{sign: signalFinish}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 在当前回合执行完毕后停止回合进程
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) Stop() {
|
||||||
|
slf.tb.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh 刷新当前操作实体的行动超时时间
|
||||||
|
// - 当不在行动阶段时,将返回 time.Time 零值
|
||||||
|
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) Refresh(duration time.Duration) time.Time {
|
||||||
|
slf.tb.actionMutex.Lock()
|
||||||
|
defer slf.tb.actionMutex.Unlock()
|
||||||
|
if slf.tb.actioning {
|
||||||
|
slf.tb.actioning = false
|
||||||
|
slf.tb.signal <- signal{sign: signalRefresh, data: duration}
|
||||||
|
return time.Now().Add(duration)
|
||||||
|
}
|
||||||
|
return time.Time{}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package fight
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
|
||||||
|
type (
|
||||||
|
TurnBasedEntitySwitchEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerAction[CampID, EntityID, Camp, Entity])
|
||||||
|
TurnBasedEntityActionTimeoutEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity])
|
||||||
|
TurnBasedEntityActionFinishEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity])
|
||||||
|
TurnBasedEntityActionSubmitEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity])
|
||||||
|
TurnBasedRoundChangeEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity])
|
||||||
|
)
|
||||||
|
|
||||||
|
type turnBasedEvents[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||||
|
entitySwitchEventHandlers []TurnBasedEntitySwitchEventHandler[CampID, EntityID, Camp, Entity]
|
||||||
|
actionTimeoutEventHandlers []TurnBasedEntityActionTimeoutEventHandler[CampID, EntityID, Camp, Entity]
|
||||||
|
actionFinishEventHandlers []TurnBasedEntityActionFinishEventHandler[CampID, EntityID, Camp, Entity]
|
||||||
|
actionSubmitEventHandlers []TurnBasedEntityActionSubmitEventHandler[CampID, EntityID, Camp, Entity]
|
||||||
|
roundChangeEventHandlers []TurnBasedRoundChangeEventHandler[CampID, EntityID, Camp, Entity]
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegTurnBasedEntitySwitchEvent 注册回合制实体切换事件处理函数,该处理函数将在切换到实体切换为操作时机时触发
|
||||||
|
// - 刚函数通常仅用于告知当前操作实体已经完成切换,适合做一些前置校验,但不应该在该函数中执行长时间阻塞操作
|
||||||
|
// - 操作计时将在该函数执行完毕后开始
|
||||||
|
//
|
||||||
|
// 场景:
|
||||||
|
// - 回合开始,如果该实体被标记为已死亡,则跳过该实体
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntitySwitchEvent(handler TurnBasedEntitySwitchEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||||
|
slf.entitySwitchEventHandlers = append(slf.entitySwitchEventHandlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTurnBasedEntitySwitchEvent 触发回合制实体切换事件
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntitySwitchEvent(controller TurnBasedControllerAction[CampID, EntityID, Camp, Entity]) {
|
||||||
|
for _, handler := range slf.entitySwitchEventHandlers {
|
||||||
|
handler(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegTurnBasedEntityActionTimeoutEvent 注册回合制实体行动超时事件处理函数,该处理函数将在实体行动超时时触发
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntityActionTimeoutEvent(handler TurnBasedEntityActionTimeoutEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||||
|
slf.actionTimeoutEventHandlers = append(slf.actionTimeoutEventHandlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTurnBasedEntityActionTimeoutEvent 触发回合制实体行动超时事件
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntityActionTimeoutEvent(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]) {
|
||||||
|
for _, handler := range slf.actionTimeoutEventHandlers {
|
||||||
|
handler(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegTurnBasedEntityActionFinishEvent 注册回合制实体行动结束事件处理函数,该处理函数将在实体行动结束时触发
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntityActionFinishEvent(handler TurnBasedEntityActionFinishEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||||
|
slf.actionFinishEventHandlers = append(slf.actionFinishEventHandlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTurnBasedEntityActionFinishEvent 触发回合制实体行动结束事件
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntityActionFinishEvent(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]) {
|
||||||
|
for _, handler := range slf.actionFinishEventHandlers {
|
||||||
|
handler(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegTurnBasedEntityActionSubmitEvent 注册回合制实体行动提交事件处理函数,该处理函数将在实体行动提交时触发
|
||||||
|
// - 该事件将在实体以任意方式结束行动时触发,包括正常结束、超时结束等
|
||||||
|
// - 该事件会在原本的行动结束事件之后触发
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntityActionSubmitEvent(handler TurnBasedEntityActionSubmitEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||||
|
slf.actionSubmitEventHandlers = append(slf.actionSubmitEventHandlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTurnBasedEntityActionSubmitEvent 触发回合制实体行动提交事件
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntityActionSubmitEvent(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]) {
|
||||||
|
for _, handler := range slf.actionSubmitEventHandlers {
|
||||||
|
handler(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegTurnBasedRoundChangeEvent 注册回合制回合变更事件处理函数,该处理函数将在回合变更时触发
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedRoundChangeEvent(handler TurnBasedRoundChangeEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||||
|
slf.roundChangeEventHandlers = append(slf.roundChangeEventHandlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTurnBasedRoundChangeEvent 触发回合制回合变更事件
|
||||||
|
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedRoundChangeEvent(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]) {
|
||||||
|
for _, handler := range slf.roundChangeEventHandlers {
|
||||||
|
handler(controller)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package fight_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/game/fight"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Camp struct {
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Camp) GetId() string {
|
||||||
|
return slf.id
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity struct {
|
||||||
|
id string
|
||||||
|
speed float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Entity) GetId() string {
|
||||||
|
return slf.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTurnBased_Run(t *testing.T) {
|
||||||
|
tbi := fight.NewTurnBased[string, string, *Camp, *Entity](func(camp *Camp, entity *Entity) time.Duration {
|
||||||
|
return time.Duration(float64(time.Second) / entity.speed)
|
||||||
|
})
|
||||||
|
|
||||||
|
tbi.SetActionTimeout(func(camp *Camp, entity *Entity) time.Duration {
|
||||||
|
return time.Second * 5
|
||||||
|
})
|
||||||
|
|
||||||
|
tbi.RegTurnBasedEntityActionTimeoutEvent(func(controller fight.TurnBasedControllerInfo[string, string, *Camp, *Entity]) {
|
||||||
|
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "超时")
|
||||||
|
})
|
||||||
|
|
||||||
|
tbi.RegTurnBasedRoundChangeEvent(func(controller fight.TurnBasedControllerInfo[string, string, *Camp, *Entity]) {
|
||||||
|
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "回合切换")
|
||||||
|
})
|
||||||
|
|
||||||
|
tbi.RegTurnBasedEntitySwitchEvent(func(controller fight.TurnBasedControllerAction[string, string, *Camp, *Entity]) {
|
||||||
|
switch controller.GetEntity().GetId() {
|
||||||
|
case "1":
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
controller.Finish()
|
||||||
|
}()
|
||||||
|
case "2":
|
||||||
|
controller.Refresh(time.Second)
|
||||||
|
case "4":
|
||||||
|
controller.Stop()
|
||||||
|
}
|
||||||
|
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "开始行动")
|
||||||
|
})
|
||||||
|
|
||||||
|
tbi.AddCamp(&Camp{id: "1"}, &Entity{id: "1", speed: 1}, &Entity{id: "2", speed: 1})
|
||||||
|
tbi.AddCamp(&Camp{id: "2"}, &Entity{id: "3", speed: 1}, &Entity{id: "4", speed: 1})
|
||||||
|
|
||||||
|
tbi.Run()
|
||||||
|
}
|
|
@ -0,0 +1,434 @@
|
||||||
|
# Space
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
space 游戏中常见的空间设计,例如房间、地图等
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 包级函数定义
|
||||||
|
|
||||||
|
|函数名称|描述
|
||||||
|
|:--|:--
|
||||||
|
|[NewRoomManager](#NewRoomManager)|创建房间管理器 RoomManager 的实例
|
||||||
|
|[NewRoomControllerOptions](#NewRoomControllerOptions)|创建房间控制器选项
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[RoomController](#struct_RoomController)|对房间进行操作的控制器,由 RoomManager 接管后返回
|
||||||
|
|`STRUCT`|[RoomManager](#struct_RoomManager)|房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作
|
||||||
|
|`STRUCT`|[RoomAssumeControlEventHandle](#struct_RoomAssumeControlEventHandle)|暂无描述...
|
||||||
|
|`STRUCT`|[RoomControllerOptions](#struct_RoomControllerOptions)|暂无描述...
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
#### func NewRoomManager\[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]\]() *RoomManager[EntityID, RoomID, Entity, Room]
|
||||||
|
<span id="NewRoomManager"></span>
|
||||||
|
> 创建房间管理器 RoomManager 的实例
|
||||||
|
|
||||||
|
**示例代码:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
func ExampleNewRoomManager() {
|
||||||
|
var rm = space.NewRoomManager[string, int64, *Player, *Room]()
|
||||||
|
fmt.Println(rm == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func NewRoomControllerOptions\[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]\]() *RoomControllerOptions[EntityID, RoomID, Entity, Room]
|
||||||
|
<span id="NewRoomControllerOptions"></span>
|
||||||
|
> 创建房间控制器选项
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController"></span>
|
||||||
|
### RoomController `STRUCT`
|
||||||
|
对房间进行操作的控制器,由 RoomManager 接管后返回
|
||||||
|
```go
|
||||||
|
type RoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct {
|
||||||
|
manager *RoomManager[EntityID, RoomID, Entity, Room]
|
||||||
|
options *RoomControllerOptions[EntityID, RoomID, Entity, Room]
|
||||||
|
room Room
|
||||||
|
entities map[EntityID]Entity
|
||||||
|
entitiesRWMutex sync.RWMutex
|
||||||
|
vacancy []int
|
||||||
|
seat []*EntityID
|
||||||
|
owner *EntityID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_RoomController_HasOwner"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) HasOwner() bool
|
||||||
|
> 判断是否有房主
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_IsOwner"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) IsOwner(entityId EntityID) bool
|
||||||
|
> 判断是否为房主
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetOwner"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetOwner() Entity
|
||||||
|
> 获取房主
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetOwnerID"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetOwnerID() EntityID
|
||||||
|
> 获取房主 ID
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetOwnerExist"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetOwnerExist() ( Entity, bool)
|
||||||
|
> 获取房间,并返回房主是否存在的状态
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_SetOwner"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) SetOwner(entityId EntityID)
|
||||||
|
> 设置房主
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_DelOwner"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) DelOwner()
|
||||||
|
> 删除房主,将房间设置为无主的状态
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_JoinSeat"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) JoinSeat(entityId EntityID, seat ...int) error
|
||||||
|
> 设置特定对象加入座位,当具体的座位不存在的时候,将会自动分配座位
|
||||||
|
> - 当目标座位存在玩家或未添加到房间中的时候,将会返回错误
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_LeaveSeat"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) LeaveSeat(entityId EntityID)
|
||||||
|
> 离开座位
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetSeat"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetSeat(entityId EntityID) int
|
||||||
|
> 获取座位
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetFirstNotEmptySeat"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetFirstNotEmptySeat() int
|
||||||
|
> 获取第一个非空座位号,如果没有非空座位,将返回 UnknownSeat
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetFirstEmptySeatEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetFirstEmptySeatEntity() (entity Entity)
|
||||||
|
> 获取第一个空座位上的实体,如果没有空座位,将返回空实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetRandomEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetRandomEntity() (entity Entity)
|
||||||
|
> 获取随机实体,如果房间中没有实体,将返回空实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetNotEmptySeat"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetNotEmptySeat() []int
|
||||||
|
> 获取非空座位
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetEmptySeat"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetEmptySeat() []int
|
||||||
|
> 获取空座位
|
||||||
|
> - 空座位需要在有对象离开座位后才可能出现
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_HasSeat"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) HasSeat(entityId EntityID) bool
|
||||||
|
> 判断是否有座位
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetSeatEntityCount"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetSeatEntityCount() int
|
||||||
|
> 获取座位上的实体数量
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetSeatEntities"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetSeatEntities() map[EntityID]Entity
|
||||||
|
> 获取座位上的实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetSeatEntitiesByOrdered"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetSeatEntitiesByOrdered() []Entity
|
||||||
|
> 有序的获取座位上的实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetSeatEntitiesByOrderedAndContainsEmpty"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetSeatEntitiesByOrderedAndContainsEmpty() []Entity
|
||||||
|
> 获取有序的座位上的实体,包含空座位
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetSeatEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetSeatEntity(seat int) (entity Entity)
|
||||||
|
> 获取座位上的实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_ContainEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) ContainEntity(id EntityID) bool
|
||||||
|
> 房间内是否包含实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetRoom"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetRoom() Room
|
||||||
|
> 获取原始房间实例,该实例为被接管的房间的原始实例
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetEntities"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetEntities() map[EntityID]Entity
|
||||||
|
> 获取所有实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_HasEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) HasEntity(id EntityID) bool
|
||||||
|
> 判断是否有实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetEntity(id EntityID) Entity
|
||||||
|
> 获取实体
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetEntityExist"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetEntityExist(id EntityID) ( Entity, bool)
|
||||||
|
> 获取实体,并返回实体是否存在的状态
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetEntityIDs"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetEntityIDs() []EntityID
|
||||||
|
> 获取所有实体ID
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetEntityCount"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetEntityCount() int
|
||||||
|
> 获取实体数量
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_ChangePassword"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) ChangePassword(password *string)
|
||||||
|
> 修改房间密码
|
||||||
|
> - 当房间密码为 nil 时,将会取消密码
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_AddEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) AddEntity(entity Entity) error
|
||||||
|
> 添加实体,如果房间存在密码,应使用 AddEntityByPassword 函数进行添加,否则将始终返回 ErrRoomPasswordNotMatch 错误
|
||||||
|
> - 当房间已满时,将会返回 ErrRoomFull 错误
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_AddEntityByPassword"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) AddEntityByPassword(entity Entity, password string) error
|
||||||
|
> 通过房间密码添加实体到该房间中
|
||||||
|
> - 当未设置房间密码时,password 参数将会被忽略
|
||||||
|
> - 当房间密码不匹配时,将会返回 ErrRoomPasswordNotMatch 错误
|
||||||
|
> - 当房间已满时,将会返回 ErrRoomFull 错误
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_RemoveEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) RemoveEntity(id EntityID)
|
||||||
|
> 移除实体
|
||||||
|
> - 当实体被移除时如果实体在座位上,将会自动离开座位
|
||||||
|
> - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_RemoveAllEntities"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) RemoveAllEntities()
|
||||||
|
> 移除该房间中的所有实体
|
||||||
|
> - 当实体被移除时如果实体在座位上,将会自动离开座位
|
||||||
|
> - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_Destroy"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) Destroy()
|
||||||
|
> 销毁房间,房间会从 RoomManager 中移除,同时所有房间的实体、座位等数据都会被清空
|
||||||
|
> - 该函数与 RoomManager.DestroyRoom 相同,RoomManager.DestroyRoom 函数为该函数的快捷方式
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetRoomManager"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetRoomManager() *RoomManager[EntityID, RoomID, Entity, Room]
|
||||||
|
> 获取该房间控制器所属的房间管理器
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_GetRoomID"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) GetRoomID() RoomID
|
||||||
|
> 获取房间 ID
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomController_Broadcast"></span>
|
||||||
|
|
||||||
|
#### func (*RoomController) Broadcast(handler func ( Entity), conditions ...func ( Entity) bool)
|
||||||
|
> 广播,该函数会将所有房间中满足 conditions 的对象传入 handler 中进行处理
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager"></span>
|
||||||
|
### RoomManager `STRUCT`
|
||||||
|
房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作
|
||||||
|
- 该实例是线程安全的
|
||||||
|
```go
|
||||||
|
type RoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct {
|
||||||
|
*roomManagerEvents[EntityID, RoomID, Entity, Room]
|
||||||
|
roomsRWMutex sync.RWMutex
|
||||||
|
rooms map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_RoomManager_AssumeControl"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) AssumeControl(room Room, options ...*RoomControllerOptions[EntityID, RoomID, Entity, Room]) *RoomController[EntityID, RoomID, Entity, Room]
|
||||||
|
> 将房间控制权交由 RoomManager 接管,返回 RoomController 实例
|
||||||
|
> - 当任何房间需要被 RoomManager 管理时,都应该调用该方法获取到 RoomController 实例后进行操作
|
||||||
|
> - 房间被接管后需要在释放房间控制权时调用 RoomController.Destroy 方法,否则将会导致 RoomManager 一直持有房间资源
|
||||||
|
|
||||||
|
**示例代码:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
func ExampleRoomManager_AssumeControl() {
|
||||||
|
var rm = space.NewRoomManager[string, int64, *Player, *Room]()
|
||||||
|
var room = &Room{Id: 1}
|
||||||
|
var controller = rm.AssumeControl(room)
|
||||||
|
if err := controller.AddEntity(&Player{Id: "1"}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(controller.GetEntityCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_DestroyRoom"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) DestroyRoom(id RoomID)
|
||||||
|
> 销毁房间,该函数为 RoomController.Destroy 的快捷方式
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_GetRoom"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) GetRoom(id RoomID) *RoomController[EntityID, RoomID, Entity, Room]
|
||||||
|
> 通过房间 ID 获取对应房间的控制器 RoomController,当房间不存在时将返回 nil
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_GetRooms"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) GetRooms() map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]
|
||||||
|
> 获取包含所有房间 ID 到对应控制器 RoomController 的映射
|
||||||
|
> - 返回值的 map 为拷贝对象,可安全的对其进行增删等操作
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_GetRoomCount"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) GetRoomCount() int
|
||||||
|
> 获取房间管理器接管的房间数量
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_GetRoomIDs"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) GetRoomIDs() []RoomID
|
||||||
|
> 获取房间管理器接管的所有房间 ID
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_HasEntity"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) HasEntity(entityId EntityID) bool
|
||||||
|
> 判断特定对象是否在任一房间中,当对象不在任一房间中时将返回 false
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_GetEntityRooms"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]
|
||||||
|
> 获取特定对象所在的房间,返回值为房间 ID 到对应控制器 RoomController 的映射
|
||||||
|
> - 由于一个对象可能在多个房间中,因此返回值为 map 类型
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomManager_Broadcast"></span>
|
||||||
|
|
||||||
|
#### func (*RoomManager) Broadcast(handler func ( Entity), conditions ...func ( Entity) bool)
|
||||||
|
> 向所有房间对象广播消息,该方法将会遍历所有房间控制器并调用 RoomController.Broadcast 方法
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomAssumeControlEventHandle"></span>
|
||||||
|
### RoomAssumeControlEventHandle `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type RoomAssumeControlEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room])
|
||||||
|
```
|
||||||
|
<span id="struct_RoomControllerOptions"></span>
|
||||||
|
### RoomControllerOptions `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type RoomControllerOptions[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct {
|
||||||
|
maxEntityCount *int
|
||||||
|
password *string
|
||||||
|
ownerInherit bool
|
||||||
|
ownerInheritHandler func(controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_RoomControllerOptions_WithOwnerInherit"></span>
|
||||||
|
|
||||||
|
#### func (*RoomControllerOptions) WithOwnerInherit(inherit bool, inheritHandler ...func (controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID) *RoomControllerOptions[EntityID, RoomID, Entity, Room]
|
||||||
|
> 设置房间所有者是否继承,默认为 false
|
||||||
|
> - inherit: 是否继承,当未设置 inheritHandler 且 inherit 为 true 时,将会按照随机或根据座位号顺序继承房间所有者
|
||||||
|
> - inheritHandler: 继承处理函数,当 inherit 为 true 时,该函数将会被调用,传入当前房间中的所有实体,返回值为新的房间所有者
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomControllerOptions_WithMaxEntityCount"></span>
|
||||||
|
|
||||||
|
#### func (*RoomControllerOptions) WithMaxEntityCount(maxEntityCount int) *RoomControllerOptions[EntityID, RoomID, Entity, Room]
|
||||||
|
> 设置房间最大实体数量
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RoomControllerOptions_WithPassword"></span>
|
||||||
|
|
||||||
|
#### func (*RoomControllerOptions) WithPassword(password string) *RoomControllerOptions[EntityID, RoomID, Entity, Room]
|
||||||
|
> 设置房间密码
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package space 游戏中常见的空间设计,例如房间、地图等
|
||||||
|
package space
|
|
@ -0,0 +1,466 @@
|
||||||
|
package space
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/collection"
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const UnknownSeat = -1 // 未知座位号
|
||||||
|
|
||||||
|
func newRoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]](manager *RoomManager[EntityID, RoomID, Entity, Room], room Room, options *RoomControllerOptions[EntityID, RoomID, Entity, Room]) *RoomController[EntityID, RoomID, Entity, Room] {
|
||||||
|
controller := &RoomController[EntityID, RoomID, Entity, Room]{
|
||||||
|
manager: manager,
|
||||||
|
options: options,
|
||||||
|
entities: make(map[EntityID]Entity),
|
||||||
|
room: room,
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.roomsRWMutex.Lock()
|
||||||
|
defer manager.roomsRWMutex.Unlock()
|
||||||
|
manager.rooms[room.GetId()] = controller
|
||||||
|
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoomController 对房间进行操作的控制器,由 RoomManager 接管后返回
|
||||||
|
type RoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct {
|
||||||
|
manager *RoomManager[EntityID, RoomID, Entity, Room]
|
||||||
|
options *RoomControllerOptions[EntityID, RoomID, Entity, Room]
|
||||||
|
room Room
|
||||||
|
entities map[EntityID]Entity
|
||||||
|
entitiesRWMutex sync.RWMutex
|
||||||
|
|
||||||
|
vacancy []int // 空缺的座位
|
||||||
|
seat []*EntityID // 座位上的玩家
|
||||||
|
owner *EntityID // 房主
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasOwner 判断是否有房主
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) HasOwner() bool {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
return rc.owner != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOwner 判断是否为房主
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) IsOwner(entityId EntityID) bool {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
return rc.owner != nil && *rc.owner == entityId
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwner 获取房主
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetOwner() Entity {
|
||||||
|
return rc.GetEntity(*rc.owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwnerID 获取房主 ID
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetOwnerID() EntityID {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
return *rc.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwnerExist 获取房间,并返回房主是否存在的状态
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetOwnerExist() (Entity, bool) {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
entity, exist := rc.entities[*rc.owner]
|
||||||
|
return entity, exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOwner 设置房主
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) SetOwner(entityId EntityID) {
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
old := *rc.owner
|
||||||
|
rc.owner = &entityId
|
||||||
|
rc.manager.OnRoomOwnerChangeEvent(rc, &old, &entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelOwner 删除房主,将房间设置为无主的状态
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) DelOwner() {
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
old := *rc.owner
|
||||||
|
rc.owner = nil
|
||||||
|
rc.manager.OnRoomOwnerChangeEvent(rc, &old, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinSeat 设置特定对象加入座位,当具体的座位不存在的时候,将会自动分配座位
|
||||||
|
// - 当目标座位存在玩家或未添加到房间中的时候,将会返回错误
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) JoinSeat(entityId EntityID, seat ...int) error {
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
_, exist := rc.entities[entityId]
|
||||||
|
if !exist {
|
||||||
|
return ErrNotInRoom
|
||||||
|
}
|
||||||
|
var targetSeat int
|
||||||
|
if len(seat) > 0 {
|
||||||
|
targetSeat = seat[0]
|
||||||
|
if targetSeat < len(rc.seat) && rc.seat[targetSeat] != nil {
|
||||||
|
return ErrSeatNotEmpty
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(rc.vacancy) > 0 {
|
||||||
|
targetSeat = rc.vacancy[0]
|
||||||
|
rc.vacancy = rc.vacancy[1:]
|
||||||
|
} else {
|
||||||
|
targetSeat = len(rc.seat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetSeat >= len(rc.seat) {
|
||||||
|
rc.seat = append(rc.seat, make([]*EntityID, targetSeat-len(rc.seat)+1)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.seat[targetSeat] = &entityId
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaveSeat 离开座位
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) LeaveSeat(entityId EntityID) {
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
rc.leaveSeat(entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// leaveSeat 离开座位(无锁)
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) leaveSeat(entityId EntityID) {
|
||||||
|
for i, seat := range rc.seat {
|
||||||
|
if seat != nil && *seat == entityId {
|
||||||
|
rc.seat[i] = nil
|
||||||
|
rc.vacancy = append(rc.vacancy, i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSeat 获取座位
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetSeat(entityId EntityID) int {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
for i, seat := range rc.seat {
|
||||||
|
if seat != nil && *seat == entityId {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnknownSeat
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirstNotEmptySeat 获取第一个非空座位号,如果没有非空座位,将返回 UnknownSeat
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetFirstNotEmptySeat() int {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
for i, seat := range rc.seat {
|
||||||
|
if seat != nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnknownSeat
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirstEmptySeatEntity 获取第一个空座位上的实体,如果没有空座位,将返回空实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetFirstEmptySeatEntity() (entity Entity) {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
for _, seat := range rc.seat {
|
||||||
|
if seat == nil {
|
||||||
|
return rc.entities[*seat]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRandomEntity 获取随机实体,如果房间中没有实体,将返回空实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetRandomEntity() (entity Entity) {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
for _, entity = range rc.entities {
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotEmptySeat 获取非空座位
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetNotEmptySeat() []int {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
var seats []int
|
||||||
|
for i, player := range rc.seat {
|
||||||
|
if player != nil {
|
||||||
|
seats = append(seats, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seats
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmptySeat 获取空座位
|
||||||
|
// - 空座位需要在有对象离开座位后才可能出现
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEmptySeat() []int {
|
||||||
|
return collection.CloneSlice(rc.vacancy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSeat 判断是否有座位
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) HasSeat(entityId EntityID) bool {
|
||||||
|
return rc.GetSeat(entityId) != UnknownSeat
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSeatEntityCount 获取座位上的实体数量
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntityCount() int {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
var count int
|
||||||
|
for _, seat := range rc.seat {
|
||||||
|
if seat != nil {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSeatEntities 获取座位上的实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntities() map[EntityID]Entity {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
var entities = make(map[EntityID]Entity)
|
||||||
|
for _, entityId := range rc.seat {
|
||||||
|
if entityId != nil {
|
||||||
|
entities[*entityId] = rc.entities[*entityId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSeatEntitiesByOrdered 有序的获取座位上的实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntitiesByOrdered() []Entity {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
var entities = make([]Entity, 0, len(rc.seat))
|
||||||
|
for _, entityId := range rc.seat {
|
||||||
|
if entityId != nil {
|
||||||
|
entities = append(entities, rc.entities[*entityId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSeatEntitiesByOrderedAndContainsEmpty 获取有序的座位上的实体,包含空座位
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntitiesByOrderedAndContainsEmpty() []Entity {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
var entities = make([]Entity, len(rc.seat))
|
||||||
|
for i, entityId := range rc.seat {
|
||||||
|
if entityId != nil {
|
||||||
|
entities[i] = rc.entities[*entityId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSeatEntity 获取座位上的实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntity(seat int) (entity Entity) {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
if seat < len(rc.seat) {
|
||||||
|
eid := rc.seat[seat]
|
||||||
|
if eid != nil {
|
||||||
|
return rc.entities[*eid]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainEntity 房间内是否包含实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) ContainEntity(id EntityID) bool {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
_, exist := rc.entities[id]
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoom 获取原始房间实例,该实例为被接管的房间的原始实例
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetRoom() Room {
|
||||||
|
return rc.room
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntities 获取所有实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntities() map[EntityID]Entity {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
return collection.CloneMap(rc.entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEntity 判断是否有实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) HasEntity(id EntityID) bool {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
_, exist := rc.entities[id]
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntity 获取实体
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntity(id EntityID) Entity {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
return rc.entities[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityExist 获取实体,并返回实体是否存在的状态
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityExist(id EntityID) (Entity, bool) {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
entity, exist := rc.entities[id]
|
||||||
|
return entity, exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityIDs 获取所有实体ID
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityIDs() []EntityID {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
return collection.ConvertMapKeysToSlice(rc.entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityCount 获取实体数量
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityCount() int {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
return len(rc.entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangePassword 修改房间密码
|
||||||
|
// - 当房间密码为 nil 时,将会取消密码
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) ChangePassword(password *string) {
|
||||||
|
old := rc.options.password
|
||||||
|
rc.options.password = password
|
||||||
|
rc.manager.OnRoomChangePasswordEvent(rc, old, rc.options.password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEntity 添加实体,如果房间存在密码,应使用 AddEntityByPassword 函数进行添加,否则将始终返回 ErrRoomPasswordNotMatch 错误
|
||||||
|
// - 当房间已满时,将会返回 ErrRoomFull 错误
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) AddEntity(entity Entity) error {
|
||||||
|
if rc.options.password != nil {
|
||||||
|
return ErrRoomPasswordNotMatch
|
||||||
|
}
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
|
||||||
|
if rc.options.maxEntityCount != nil && len(rc.entities) > *rc.options.maxEntityCount {
|
||||||
|
return ErrRoomFull
|
||||||
|
}
|
||||||
|
rc.entities[entity.GetId()] = entity
|
||||||
|
|
||||||
|
rc.manager.OnRoomAddEntityEvent(rc, entity)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEntityByPassword 通过房间密码添加实体到该房间中
|
||||||
|
// - 当未设置房间密码时,password 参数将会被忽略
|
||||||
|
// - 当房间密码不匹配时,将会返回 ErrRoomPasswordNotMatch 错误
|
||||||
|
// - 当房间已满时,将会返回 ErrRoomFull 错误
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) AddEntityByPassword(entity Entity, password string) error {
|
||||||
|
if rc.options.password == nil || *rc.options.password != password {
|
||||||
|
return ErrRoomPasswordNotMatch
|
||||||
|
}
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
|
||||||
|
if rc.options.maxEntityCount != nil && len(rc.entities) > *rc.options.maxEntityCount {
|
||||||
|
return ErrRoomFull
|
||||||
|
}
|
||||||
|
rc.entities[entity.GetId()] = entity
|
||||||
|
|
||||||
|
rc.manager.OnRoomAddEntityEvent(rc, entity)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEntity 移除实体
|
||||||
|
// - 当实体被移除时如果实体在座位上,将会自动离开座位
|
||||||
|
// - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) RemoveEntity(id EntityID) {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
defer rc.entitiesRWMutex.RUnlock()
|
||||||
|
rc.removeEntity(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeEntity 移除实体(无锁)
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) removeEntity(id EntityID) {
|
||||||
|
rc.leaveSeat(id)
|
||||||
|
entity, exist := rc.entities[id]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(rc.entities, id)
|
||||||
|
if !rc.options.ownerInherit {
|
||||||
|
if rc.owner != nil && *rc.owner == id {
|
||||||
|
rc.owner = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if rc.owner != nil && *rc.owner == id {
|
||||||
|
rc.owner = rc.options.ownerInheritHandler(rc)
|
||||||
|
defer rc.manager.OnRoomOwnerChangeEvent(rc, &id, rc.owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rc.manager.OnRoomRemoveEntityEvent(rc, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllEntities 移除该房间中的所有实体
|
||||||
|
// - 当实体被移除时如果实体在座位上,将会自动离开座位
|
||||||
|
// - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) RemoveAllEntities() {
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
for id := range rc.entities {
|
||||||
|
rc.removeEntity(id)
|
||||||
|
delete(rc.entities, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy 销毁房间,房间会从 RoomManager 中移除,同时所有房间的实体、座位等数据都会被清空
|
||||||
|
// - 该函数与 RoomManager.DestroyRoom 相同,RoomManager.DestroyRoom 函数为该函数的快捷方式
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) Destroy() {
|
||||||
|
rc.manager.roomsRWMutex.Lock()
|
||||||
|
defer rc.manager.roomsRWMutex.Unlock()
|
||||||
|
|
||||||
|
delete(rc.manager.rooms, rc.room.GetId())
|
||||||
|
rc.manager.OnRoomDestroyEvent(rc)
|
||||||
|
|
||||||
|
rc.entitiesRWMutex.Lock()
|
||||||
|
defer rc.entitiesRWMutex.Unlock()
|
||||||
|
|
||||||
|
for eid := range rc.entities {
|
||||||
|
rc.removeEntity(eid)
|
||||||
|
delete(rc.entities, eid)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.entities = make(map[EntityID]Entity)
|
||||||
|
rc.seat = rc.seat[:]
|
||||||
|
rc.vacancy = rc.vacancy[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomManager 获取该房间控制器所属的房间管理器
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] {
|
||||||
|
return rc.manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomID 获取房间 ID
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetRoomID() RoomID {
|
||||||
|
return rc.room.GetId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast 广播,该函数会将所有房间中满足 conditions 的对象传入 handler 中进行处理
|
||||||
|
func (rc *RoomController[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) {
|
||||||
|
rc.entitiesRWMutex.RLock()
|
||||||
|
entities := collection.CloneMap(rc.entities)
|
||||||
|
rc.entitiesRWMutex.RUnlock()
|
||||||
|
for _, entity := range entities {
|
||||||
|
for _, condition := range conditions {
|
||||||
|
if !condition(entity) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler(entity)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package space
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrRoomFull 房间已满
|
||||||
|
ErrRoomFull = errors.New("room is full")
|
||||||
|
// ErrSeatNotEmpty 座位上已经有实体
|
||||||
|
ErrSeatNotEmpty = errors.New("seat is not empty")
|
||||||
|
// ErrNotInRoom 实体不在房间中
|
||||||
|
ErrNotInRoom = errors.New("not in room")
|
||||||
|
// ErrRoomPasswordNotMatch 房间密码不匹配
|
||||||
|
ErrRoomPasswordNotMatch = errors.New("room password not match")
|
||||||
|
// ErrPermissionDenied 权限不足
|
||||||
|
ErrPermissionDenied = errors.New("permission denied")
|
||||||
|
)
|
|
@ -0,0 +1,110 @@
|
||||||
|
package space
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/collection"
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRoomManager 创建房间管理器 RoomManager 的实例
|
||||||
|
func NewRoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]]() *RoomManager[EntityID, RoomID, Entity, Room] {
|
||||||
|
return &RoomManager[EntityID, RoomID, Entity, Room]{
|
||||||
|
roomManagerEvents: new(roomManagerEvents[EntityID, RoomID, Entity, Room]),
|
||||||
|
rooms: make(map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoomManager 房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作
|
||||||
|
// - 该实例是线程安全的
|
||||||
|
type RoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct {
|
||||||
|
*roomManagerEvents[EntityID, RoomID, Entity, Room]
|
||||||
|
roomsRWMutex sync.RWMutex
|
||||||
|
rooms map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssumeControl 将房间控制权交由 RoomManager 接管,返回 RoomController 实例
|
||||||
|
// - 当任何房间需要被 RoomManager 管理时,都应该调用该方法获取到 RoomController 实例后进行操作
|
||||||
|
// - 房间被接管后需要在释放房间控制权时调用 RoomController.Destroy 方法,否则将会导致 RoomManager 一直持有房间资源
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) AssumeControl(room Room, options ...*RoomControllerOptions[EntityID, RoomID, Entity, Room]) *RoomController[EntityID, RoomID, Entity, Room] {
|
||||||
|
controller := newRoomController(rm, room, mergeRoomControllerOptions(options...))
|
||||||
|
rm.OnRoomAssumeControlEvent(controller)
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyRoom 销毁房间,该函数为 RoomController.Destroy 的快捷方式
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) DestroyRoom(id RoomID) {
|
||||||
|
rm.roomsRWMutex.Lock()
|
||||||
|
room, exist := rm.rooms[id]
|
||||||
|
rm.roomsRWMutex.Unlock()
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
room.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoom 通过房间 ID 获取对应房间的控制器 RoomController,当房间不存在时将返回 nil
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoom(id RoomID) *RoomController[EntityID, RoomID, Entity, Room] {
|
||||||
|
rm.roomsRWMutex.RLock()
|
||||||
|
defer rm.roomsRWMutex.RUnlock()
|
||||||
|
return rm.rooms[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRooms 获取包含所有房间 ID 到对应控制器 RoomController 的映射
|
||||||
|
// - 返回值的 map 为拷贝对象,可安全的对其进行增删等操作
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRooms() map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] {
|
||||||
|
rm.roomsRWMutex.RLock()
|
||||||
|
defer rm.roomsRWMutex.RUnlock()
|
||||||
|
return collection.CloneMap(rm.rooms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomCount 获取房间管理器接管的房间数量
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomCount() int {
|
||||||
|
rm.roomsRWMutex.RLock()
|
||||||
|
defer rm.roomsRWMutex.RUnlock()
|
||||||
|
return len(rm.rooms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomIDs 获取房间管理器接管的所有房间 ID
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomIDs() []RoomID {
|
||||||
|
rm.roomsRWMutex.RLock()
|
||||||
|
defer rm.roomsRWMutex.RUnlock()
|
||||||
|
return collection.ConvertMapKeysToSlice(rm.rooms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEntity 判断特定对象是否在任一房间中,当对象不在任一房间中时将返回 false
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) HasEntity(entityId EntityID) bool {
|
||||||
|
rm.roomsRWMutex.RLock()
|
||||||
|
rooms := collection.CloneMap(rm.rooms)
|
||||||
|
rm.roomsRWMutex.RUnlock()
|
||||||
|
for _, room := range rooms {
|
||||||
|
if room.HasEntity(entityId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityRooms 获取特定对象所在的房间,返回值为房间 ID 到对应控制器 RoomController 的映射
|
||||||
|
// - 由于一个对象可能在多个房间中,因此返回值为 map 类型
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] {
|
||||||
|
rm.roomsRWMutex.RLock()
|
||||||
|
rooms := collection.CloneMap(rm.rooms)
|
||||||
|
rm.roomsRWMutex.RUnlock()
|
||||||
|
var result = make(map[RoomID]*RoomController[EntityID, RoomID, Entity, Room])
|
||||||
|
for id, room := range rooms {
|
||||||
|
if room.HasEntity(entityId) {
|
||||||
|
result[id] = room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast 向所有房间对象广播消息,该方法将会遍历所有房间控制器并调用 RoomController.Broadcast 方法
|
||||||
|
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) {
|
||||||
|
rm.roomsRWMutex.RLock()
|
||||||
|
rooms := collection.CloneMap(rm.rooms)
|
||||||
|
rm.roomsRWMutex.RUnlock()
|
||||||
|
for _, room := range rooms {
|
||||||
|
room.Broadcast(handler, conditions...)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package space
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
|
||||||
|
type (
|
||||||
|
RoomAssumeControlEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room])
|
||||||
|
RoomDestroyEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room])
|
||||||
|
RoomAddEntityEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity)
|
||||||
|
RoomRemoveEntityEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity)
|
||||||
|
RoomChangePasswordEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room], oldPassword, password *string)
|
||||||
|
RoomOwnerChangeEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room], oldOwner, owner *EntityID)
|
||||||
|
)
|
||||||
|
|
||||||
|
type roomManagerEvents[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct {
|
||||||
|
roomAssumeControlEventHandles []RoomAssumeControlEventHandle[EntityID, RoomID, Entity, Room]
|
||||||
|
roomDestroyEventHandles []RoomDestroyEventHandle[EntityID, RoomID, Entity, Room]
|
||||||
|
roomAddEntityEventHandles []RoomAddEntityEventHandle[EntityID, RoomID, Entity, Room]
|
||||||
|
roomRemoveEntityEventHandles []RoomRemoveEntityEventHandle[EntityID, RoomID, Entity, Room]
|
||||||
|
roomChangePasswordEventHandles []RoomChangePasswordEventHandle[EntityID, RoomID, Entity, Room]
|
||||||
|
roomOwnerChangeEventHandles []RoomOwnerChangeEventHandle[EntityID, RoomID, Entity, Room]
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegRoomOwnerChangeEvent 注册房间所有者变更事件,当触发事件时,房间所有者已经被修改
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomOwnerChangeEvent(handle RoomOwnerChangeEventHandle[EntityID, RoomID, Entity, Room]) {
|
||||||
|
rme.roomOwnerChangeEventHandles = append(rme.roomOwnerChangeEventHandles, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRoomOwnerChangeEvent 房间所有者变更事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomOwnerChangeEvent(controller *RoomController[EntityID, RoomID, Entity, Room], oldOwner, owner *EntityID) {
|
||||||
|
for _, handle := range rme.roomOwnerChangeEventHandles {
|
||||||
|
handle(controller, oldOwner, owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegRoomAssumeControlEvent 注册房间接管事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomAssumeControlEvent(handle RoomAssumeControlEventHandle[EntityID, RoomID, Entity, Room]) {
|
||||||
|
rme.roomAssumeControlEventHandles = append(rme.roomAssumeControlEventHandles, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRoomAssumeControlEvent 房间接管事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomAssumeControlEvent(controller *RoomController[EntityID, RoomID, Entity, Room]) {
|
||||||
|
for _, handle := range rme.roomAssumeControlEventHandles {
|
||||||
|
handle(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegRoomDestroyEvent 注册房间销毁事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomDestroyEvent(handle RoomDestroyEventHandle[EntityID, RoomID, Entity, Room]) {
|
||||||
|
rme.roomDestroyEventHandles = append(rme.roomDestroyEventHandles, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRoomDestroyEvent 房间销毁事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomDestroyEvent(controller *RoomController[EntityID, RoomID, Entity, Room]) {
|
||||||
|
for _, handle := range rme.roomDestroyEventHandles {
|
||||||
|
handle(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegRoomAddEntityEvent 注册房间添加对象事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomAddEntityEvent(handle RoomAddEntityEventHandle[EntityID, RoomID, Entity, Room]) {
|
||||||
|
rme.roomAddEntityEventHandles = append(rme.roomAddEntityEventHandles, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRoomAddEntityEvent 房间添加对象事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomAddEntityEvent(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity) {
|
||||||
|
for _, handle := range rme.roomAddEntityEventHandles {
|
||||||
|
handle(controller, entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegRoomRemoveEntityEvent 注册房间移除对象事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomRemoveEntityEvent(handle RoomRemoveEntityEventHandle[EntityID, RoomID, Entity, Room]) {
|
||||||
|
rme.roomRemoveEntityEventHandles = append(rme.roomRemoveEntityEventHandles, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRoomRemoveEntityEvent 房间移除对象事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomRemoveEntityEvent(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity) {
|
||||||
|
for _, handle := range rme.roomRemoveEntityEventHandles {
|
||||||
|
handle(controller, entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegRoomChangePasswordEvent 注册房间修改密码事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomChangePasswordEvent(handle RoomChangePasswordEventHandle[EntityID, RoomID, Entity, Room]) {
|
||||||
|
rme.roomChangePasswordEventHandles = append(rme.roomChangePasswordEventHandles, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRoomChangePasswordEvent 房间修改密码事件
|
||||||
|
func (rme *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomChangePasswordEvent(controller *RoomController[EntityID, RoomID, Entity, Room], oldPassword, password *string) {
|
||||||
|
for _, handle := range rme.roomChangePasswordEventHandles {
|
||||||
|
handle(controller, oldPassword, password)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package space_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/game/space"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Room struct {
|
||||||
|
Id int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Room) GetId() int64 {
|
||||||
|
return r.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
Id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player) GetId() string {
|
||||||
|
return p.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewRoomManager() {
|
||||||
|
var rm = space.NewRoomManager[string, int64, *Player, *Room]()
|
||||||
|
fmt.Println(rm == nil)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRoomManager_AssumeControl() {
|
||||||
|
var rm = space.NewRoomManager[string, int64, *Player, *Room]()
|
||||||
|
var room = &Room{Id: 1}
|
||||||
|
var controller = rm.AssumeControl(room)
|
||||||
|
|
||||||
|
if err := controller.AddEntity(&Player{Id: "1"}); err != nil {
|
||||||
|
// 房间密码不匹配或者房间已满
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(controller.GetEntityCount())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 1
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package space
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
|
||||||
|
// NewRoomControllerOptions 创建房间控制器选项
|
||||||
|
func NewRoomControllerOptions[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]]() *RoomControllerOptions[EntityID, RoomID, Entity, Room] {
|
||||||
|
return &RoomControllerOptions[EntityID, RoomID, Entity, Room]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeRoomControllerOptions 合并房间控制器选项
|
||||||
|
func mergeRoomControllerOptions[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]](options ...*RoomControllerOptions[EntityID, RoomID, Entity, Room]) *RoomControllerOptions[EntityID, RoomID, Entity, Room] {
|
||||||
|
result := NewRoomControllerOptions[EntityID, RoomID, Entity, Room]()
|
||||||
|
for _, option := range options {
|
||||||
|
if option.maxEntityCount != nil {
|
||||||
|
result.maxEntityCount = option.maxEntityCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoomControllerOptions[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct {
|
||||||
|
maxEntityCount *int // 房间最大实体数量
|
||||||
|
password *string // 房间密码
|
||||||
|
ownerInherit bool // 房间所有者是否继承
|
||||||
|
ownerInheritHandler func(controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID // 房间所有者继承处理函数
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOwnerInherit 设置房间所有者是否继承,默认为 false
|
||||||
|
// - inherit: 是否继承,当未设置 inheritHandler 且 inherit 为 true 时,将会按照随机或根据座位号顺序继承房间所有者
|
||||||
|
// - inheritHandler: 继承处理函数,当 inherit 为 true 时,该函数将会被调用,传入当前房间中的所有实体,返回值为新的房间所有者
|
||||||
|
func (rco *RoomControllerOptions[EntityID, RoomID, Entity, Room]) WithOwnerInherit(inherit bool, inheritHandler ...func(controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID) *RoomControllerOptions[EntityID, RoomID, Entity, Room] {
|
||||||
|
rco.ownerInherit = inherit
|
||||||
|
if len(inheritHandler) > 0 {
|
||||||
|
rco.ownerInheritHandler = inheritHandler[0]
|
||||||
|
} else if inherit {
|
||||||
|
rco.ownerInheritHandler = func(controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID {
|
||||||
|
if e := controller.GetFirstEmptySeatEntity(); !generic.IsNil(e) {
|
||||||
|
var id = e.GetId()
|
||||||
|
return &id
|
||||||
|
}
|
||||||
|
if e := controller.GetRandomEntity(); !generic.IsNil(e) {
|
||||||
|
var id = e.GetId()
|
||||||
|
return &id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rco
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxEntityCount 设置房间最大实体数量
|
||||||
|
func (rco *RoomControllerOptions[EntityID, RoomID, Entity, Room]) WithMaxEntityCount(maxEntityCount int) *RoomControllerOptions[EntityID, RoomID, Entity, Room] {
|
||||||
|
if maxEntityCount > 0 {
|
||||||
|
rco.maxEntityCount = &maxEntityCount
|
||||||
|
}
|
||||||
|
return rco
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPassword 设置房间密码
|
||||||
|
func (rco *RoomControllerOptions[EntityID, RoomID, Entity, Room]) WithPassword(password string) *RoomControllerOptions[EntityID, RoomID, Entity, Room] {
|
||||||
|
if password != "" {
|
||||||
|
rco.password = &password
|
||||||
|
}
|
||||||
|
return rco
|
||||||
|
}
|
|
@ -0,0 +1,387 @@
|
||||||
|
# Task
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 包级函数定义
|
||||||
|
|
||||||
|
|函数名称|描述
|
||||||
|
|:--|:--
|
||||||
|
|[Cond](#Cond)|创建任务条件
|
||||||
|
|[RegisterRefreshTaskCounterEvent](#RegisterRefreshTaskCounterEvent)|注册特定任务类型的刷新任务计数器事件处理函数
|
||||||
|
|[OnRefreshTaskCounterEvent](#OnRefreshTaskCounterEvent)|触发特定任务类型的刷新任务计数器事件
|
||||||
|
|[RegisterRefreshTaskConditionEvent](#RegisterRefreshTaskConditionEvent)|注册特定任务类型的刷新任务条件事件处理函数
|
||||||
|
|[OnRefreshTaskConditionEvent](#OnRefreshTaskConditionEvent)|触发特定任务类型的刷新任务条件事件
|
||||||
|
|[WithType](#WithType)|设置任务类型
|
||||||
|
|[WithCondition](#WithCondition)|设置任务完成条件,当满足条件时,任务状态为完成
|
||||||
|
|[WithCounter](#WithCounter)|设置任务计数器,当计数器达到要求时,任务状态为完成
|
||||||
|
|[WithOverflowCounter](#WithOverflowCounter)|设置可溢出的任务计数器,当计数器达到要求时,任务状态为完成
|
||||||
|
|[WithDeadline](#WithDeadline)|设置任务截止时间,超过截至时间并且任务未完成时,任务状态为失败
|
||||||
|
|[WithLimitedDuration](#WithLimitedDuration)|设置任务限时,超过限时时间并且任务未完成时,任务状态为失败
|
||||||
|
|[NewTask](#NewTask)|生成任务
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[Condition](#struct_Condition)|任务条件
|
||||||
|
|`STRUCT`|[RefreshTaskCounterEventHandler](#struct_RefreshTaskCounterEventHandler)|暂无描述...
|
||||||
|
|`STRUCT`|[Option](#struct_Option)|任务选项
|
||||||
|
|`STRUCT`|[Status](#struct_Status)|暂无描述...
|
||||||
|
|`STRUCT`|[Task](#struct_Task)|是对任务信息进行描述和处理的结构体
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
#### func Cond(k any, v any) Condition
|
||||||
|
<span id="Cond"></span>
|
||||||
|
> 创建任务条件
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>查看 / 收起单元测试</summary>
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
func TestCond(t *testing.T) {
|
||||||
|
task := NewTask(WithType("T"), WithCounter(5), WithCondition(Cond("N", 5).Cond("M", 10)))
|
||||||
|
task.AssignConditionValueAndRefresh("N", 5)
|
||||||
|
task.AssignConditionValueAndRefresh("M", 10)
|
||||||
|
RegisterRefreshTaskCounterEvent[*Player](task.Type, func(taskType string, trigger *Player, count int64) {
|
||||||
|
fmt.Println("Player", count)
|
||||||
|
for _, t := range trigger.tasks[taskType] {
|
||||||
|
fmt.Println(t.CurrCount, t.IncrementCounter(count).Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
RegisterRefreshTaskConditionEvent[*Player](task.Type, func(taskType string, trigger *Player, condition Condition) {
|
||||||
|
fmt.Println("Player", condition)
|
||||||
|
for _, t := range trigger.tasks[taskType] {
|
||||||
|
fmt.Println(t.CurrCount, t.AssignConditionValueAndRefresh("N", 5).Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
RegisterRefreshTaskCounterEvent[*Monster](task.Type, func(taskType string, trigger *Monster, count int64) {
|
||||||
|
fmt.Println("Monster", count)
|
||||||
|
})
|
||||||
|
player := &Player{tasks: map[string][]*Task{task.Type: {task}}}
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, player, 1)
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, player, 2)
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, player, 3)
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, new(Monster), 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegisterRefreshTaskCounterEvent\[Trigger any\](taskType string, handler RefreshTaskCounterEventHandler[Trigger])
|
||||||
|
<span id="RegisterRefreshTaskCounterEvent"></span>
|
||||||
|
> 注册特定任务类型的刷新任务计数器事件处理函数
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnRefreshTaskCounterEvent(taskType string, trigger any, count int64)
|
||||||
|
<span id="OnRefreshTaskCounterEvent"></span>
|
||||||
|
> 触发特定任务类型的刷新任务计数器事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegisterRefreshTaskConditionEvent\[Trigger any\](taskType string, handler RefreshTaskConditionEventHandler[Trigger])
|
||||||
|
<span id="RegisterRefreshTaskConditionEvent"></span>
|
||||||
|
> 注册特定任务类型的刷新任务条件事件处理函数
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func OnRefreshTaskConditionEvent(taskType string, trigger any, condition Condition)
|
||||||
|
<span id="OnRefreshTaskConditionEvent"></span>
|
||||||
|
> 触发特定任务类型的刷新任务条件事件
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func WithType(taskType string) Option
|
||||||
|
<span id="WithType"></span>
|
||||||
|
> 设置任务类型
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func WithCondition(condition Condition) Option
|
||||||
|
<span id="WithCondition"></span>
|
||||||
|
> 设置任务完成条件,当满足条件时,任务状态为完成
|
||||||
|
> - 任务条件值需要变更时可通过 Task.AssignConditionValueAndRefresh 方法变更
|
||||||
|
> - 当多次设置该选项时,后面的设置会覆盖之前的设置
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func WithCounter(counter int64, initCount ...int64) Option
|
||||||
|
<span id="WithCounter"></span>
|
||||||
|
> 设置任务计数器,当计数器达到要求时,任务状态为完成
|
||||||
|
> - 一些场景下,任务计数器可能会溢出,此时可通过 WithOverflowCounter 设置可溢出的任务计数器
|
||||||
|
> - 当多次设置该选项时,后面的设置会覆盖之前的设置
|
||||||
|
> - 如果需要初始化计数器的值,可通过 initCount 参数设置
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func WithOverflowCounter(counter int64, initCount ...int64) Option
|
||||||
|
<span id="WithOverflowCounter"></span>
|
||||||
|
> 设置可溢出的任务计数器,当计数器达到要求时,任务状态为完成
|
||||||
|
> - 当多次设置该选项时,后面的设置会覆盖之前的设置
|
||||||
|
> - 如果需要初始化计数器的值,可通过 initCount 参数设置
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func WithDeadline(deadline time.Time) Option
|
||||||
|
<span id="WithDeadline"></span>
|
||||||
|
> 设置任务截止时间,超过截至时间并且任务未完成时,任务状态为失败
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func WithLimitedDuration(start time.Time, duration time.Duration) Option
|
||||||
|
<span id="WithLimitedDuration"></span>
|
||||||
|
> 设置任务限时,超过限时时间并且任务未完成时,任务状态为失败
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func NewTask(options ...Option) *Task
|
||||||
|
<span id="NewTask"></span>
|
||||||
|
> 生成任务
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition"></span>
|
||||||
|
### Condition `STRUCT`
|
||||||
|
任务条件
|
||||||
|
```go
|
||||||
|
type Condition map[any]any
|
||||||
|
```
|
||||||
|
<span id="struct_Condition_Cond"></span>
|
||||||
|
|
||||||
|
#### func (Condition) Cond(k any, v any) Condition
|
||||||
|
> 创建任务条件
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetString"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetString(key any) string
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetInt"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetInt(key any) int
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetInt8"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetInt8(key any) int8
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetInt16"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetInt16(key any) int16
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetInt32"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetInt32(key any) int32
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetInt64"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetInt64(key any) int64
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetUint"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetUint(key any) uint
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetUint8"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetUint8(key any) uint8
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetUint16"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetUint16(key any) uint16
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetUint32"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetUint32(key any) uint32
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetUint64"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetUint64(key any) uint64
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetFloat32"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetFloat32(key any) float32
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetFloat64"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetFloat64(key any) float64
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetBool"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetBool(key any) bool
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetTime"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetTime(key any) time.Time
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetDuration"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetDuration(key any) time.Duration
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetByte"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetByte(key any) byte
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetBytes"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetBytes(key any) []byte
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetRune"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetRune(key any) rune
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetRunes"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetRunes(key any) []rune
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Condition_GetAny"></span>
|
||||||
|
|
||||||
|
#### func (Condition) GetAny(key any) any
|
||||||
|
> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_RefreshTaskCounterEventHandler"></span>
|
||||||
|
### RefreshTaskCounterEventHandler `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type RefreshTaskCounterEventHandler[Trigger any] func(taskType string, trigger Trigger, count int64)
|
||||||
|
```
|
||||||
|
<span id="struct_Option"></span>
|
||||||
|
### Option `STRUCT`
|
||||||
|
任务选项
|
||||||
|
```go
|
||||||
|
type Option func(task *Task)
|
||||||
|
```
|
||||||
|
<span id="struct_Status"></span>
|
||||||
|
### Status `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Status byte
|
||||||
|
```
|
||||||
|
<span id="struct_Status_String"></span>
|
||||||
|
|
||||||
|
#### func (Status) String() string
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task"></span>
|
||||||
|
### Task `STRUCT`
|
||||||
|
是对任务信息进行描述和处理的结构体
|
||||||
|
```go
|
||||||
|
type Task struct {
|
||||||
|
Type string
|
||||||
|
Status Status
|
||||||
|
Cond Condition
|
||||||
|
CondValue map[any]any
|
||||||
|
Counter int64
|
||||||
|
CurrCount int64
|
||||||
|
CurrOverflow bool
|
||||||
|
Deadline time.Time
|
||||||
|
StartTime time.Time
|
||||||
|
LimitedDuration time.Duration
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Task_IsComplete"></span>
|
||||||
|
|
||||||
|
#### func (*Task) IsComplete() bool
|
||||||
|
> 判断任务是否已完成
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_IsFailed"></span>
|
||||||
|
|
||||||
|
#### func (*Task) IsFailed() bool
|
||||||
|
> 判断任务是否已失败
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_IsReward"></span>
|
||||||
|
|
||||||
|
#### func (*Task) IsReward() bool
|
||||||
|
> 判断任务是否已领取奖励
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_ReceiveReward"></span>
|
||||||
|
|
||||||
|
#### func (*Task) ReceiveReward() bool
|
||||||
|
> 领取任务奖励,当任务状态为已完成时,才能领取奖励,此时返回 true,并且任务状态变更为已领取奖励
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_IncrementCounter"></span>
|
||||||
|
|
||||||
|
#### func (*Task) IncrementCounter(incr int64) *Task
|
||||||
|
> 增加计数器的值,当 incr 为负数时,计数器的值不会发生变化
|
||||||
|
> - 如果需要溢出计数器,可通过 WithOverflowCounter 设置可溢出的任务计数器
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_DecrementCounter"></span>
|
||||||
|
|
||||||
|
#### func (*Task) DecrementCounter(decr int64) *Task
|
||||||
|
> 减少计数器的值,当 decr 为负数时,计数器的值不会发生变化
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_AssignConditionValueAndRefresh"></span>
|
||||||
|
|
||||||
|
#### func (*Task) AssignConditionValueAndRefresh(key any, value any) *Task
|
||||||
|
> 分配条件值并刷新任务状态
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_AssignConditionValueAndRefreshByCondition"></span>
|
||||||
|
|
||||||
|
#### func (*Task) AssignConditionValueAndRefreshByCondition(condition Condition) *Task
|
||||||
|
> 分配条件值并刷新任务状态
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Task_ResetStatus"></span>
|
||||||
|
|
||||||
|
#### func (*Task) ResetStatus() *Task
|
||||||
|
> 重置任务状态
|
||||||
|
> - 该函数会将任务状态重置为已接受状态后,再刷新任务状态
|
||||||
|
> - 当任务条件变更,例如任务计数要求为 10,已经完成的情况下,将任务计数要求变更为 5 或 20,此时任务状态由于是已完成或已领取状态,不会自动刷新,需要调用该函数刷新任务状态
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,142 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Cond 创建任务条件
|
||||||
|
func Cond(k, v any) Condition {
|
||||||
|
return map[any]any{k: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition 任务条件
|
||||||
|
type Condition map[any]any
|
||||||
|
|
||||||
|
// Cond 创建任务条件
|
||||||
|
func (slf Condition) Cond(k, v any) Condition {
|
||||||
|
slf[k] = v
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetString(key any) string {
|
||||||
|
v, _ := slf[key].(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetInt(key any) int {
|
||||||
|
v, _ := slf[key].(int)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt8 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetInt8(key any) int8 {
|
||||||
|
v, _ := slf[key].(int8)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt16 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetInt16(key any) int16 {
|
||||||
|
v, _ := slf[key].(int16)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt32 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetInt32(key any) int32 {
|
||||||
|
v, _ := slf[key].(int32)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetInt64(key any) int64 {
|
||||||
|
v, _ := slf[key].(int64)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetUint(key any) uint {
|
||||||
|
v, _ := slf[key].(uint)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint8 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetUint8(key any) uint8 {
|
||||||
|
v, _ := slf[key].(uint8)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint16 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetUint16(key any) uint16 {
|
||||||
|
v, _ := slf[key].(uint16)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint32 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetUint32(key any) uint32 {
|
||||||
|
v, _ := slf[key].(uint32)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint64 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetUint64(key any) uint64 {
|
||||||
|
v, _ := slf[key].(uint64)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat32 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetFloat32(key any) float32 {
|
||||||
|
v, _ := slf[key].(float32)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat64 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetFloat64(key any) float64 {
|
||||||
|
v, _ := slf[key].(float64)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetBool(key any) bool {
|
||||||
|
v, _ := slf[key].(bool)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTime 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetTime(key any) time.Time {
|
||||||
|
v, _ := slf[key].(time.Time)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDuration 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetDuration(key any) time.Duration {
|
||||||
|
v, _ := slf[key].(time.Duration)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByte 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetByte(key any) byte {
|
||||||
|
v, _ := slf[key].(byte)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBytes 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetBytes(key any) []byte {
|
||||||
|
v, _ := slf[key].([]byte)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRune 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetRune(key any) rune {
|
||||||
|
v, _ := slf[key].(rune)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunes 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetRunes(key any) []rune {
|
||||||
|
v, _ := slf[key].([]rune)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAny 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
|
||||||
|
func (slf Condition) GetAny(key any) any {
|
||||||
|
return slf[key]
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
RefreshTaskCounterEventHandler[Trigger any] func(taskType string, trigger Trigger, count int64) // 刷新任务计数器事件处理函数
|
||||||
|
RefreshTaskConditionEventHandler[Trigger any] func(taskType string, trigger Trigger, condition Condition) // 刷新任务条件事件处理函数
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
refreshTaskCounterEventHandlers = make(map[string][]struct {
|
||||||
|
t reflect.Type
|
||||||
|
h func(taskType string, trigger any, count int64)
|
||||||
|
})
|
||||||
|
refreshTaskConditionEventHandlers = make(map[string][]struct {
|
||||||
|
t reflect.Type
|
||||||
|
h func(taskType string, trigger any, condition Condition)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterRefreshTaskCounterEvent 注册特定任务类型的刷新任务计数器事件处理函数
|
||||||
|
func RegisterRefreshTaskCounterEvent[Trigger any](taskType string, handler RefreshTaskCounterEventHandler[Trigger]) {
|
||||||
|
if refreshTaskCounterEventHandlers == nil {
|
||||||
|
refreshTaskCounterEventHandlers = make(map[string][]struct {
|
||||||
|
t reflect.Type
|
||||||
|
h func(taskType string, trigger any, count int64)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refreshTaskCounterEventHandlers[taskType] = append(refreshTaskCounterEventHandlers[taskType], struct {
|
||||||
|
t reflect.Type
|
||||||
|
h func(taskType string, trigger any, count int64)
|
||||||
|
}{reflect.TypeOf(handler).In(1), func(taskType string, trigger any, count int64) {
|
||||||
|
handler(taskType, trigger.(Trigger), count)
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRefreshTaskCounterEvent 触发特定任务类型的刷新任务计数器事件
|
||||||
|
func OnRefreshTaskCounterEvent(taskType string, trigger any, count int64) {
|
||||||
|
if handlers, exist := refreshTaskCounterEventHandlers[taskType]; exist {
|
||||||
|
for _, handler := range handlers {
|
||||||
|
if !reflect.TypeOf(trigger).AssignableTo(handler.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handler.h(taskType, trigger, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterRefreshTaskConditionEvent 注册特定任务类型的刷新任务条件事件处理函数
|
||||||
|
func RegisterRefreshTaskConditionEvent[Trigger any](taskType string, handler RefreshTaskConditionEventHandler[Trigger]) {
|
||||||
|
if refreshTaskConditionEventHandlers == nil {
|
||||||
|
refreshTaskConditionEventHandlers = make(map[string][]struct {
|
||||||
|
t reflect.Type
|
||||||
|
h func(taskType string, trigger any, condition Condition)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refreshTaskConditionEventHandlers[taskType] = append(refreshTaskConditionEventHandlers[taskType], struct {
|
||||||
|
t reflect.Type
|
||||||
|
h func(taskType string, trigger any, condition Condition)
|
||||||
|
}{reflect.TypeOf(handler).In(1), func(taskType string, trigger any, condition Condition) {
|
||||||
|
handler(taskType, trigger.(Trigger), condition)
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRefreshTaskConditionEvent 触发特定任务类型的刷新任务条件事件
|
||||||
|
func OnRefreshTaskConditionEvent(taskType string, trigger any, condition Condition) {
|
||||||
|
if handlers, exist := refreshTaskConditionEventHandlers[taskType]; exist {
|
||||||
|
for _, handler := range handlers {
|
||||||
|
if !reflect.TypeOf(trigger).AssignableTo(handler.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handler.h(taskType, trigger, condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option 任务选项
|
||||||
|
type Option func(task *Task)
|
||||||
|
|
||||||
|
// WithType 设置任务类型
|
||||||
|
func WithType(taskType string) Option {
|
||||||
|
return func(task *Task) {
|
||||||
|
task.Type = taskType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCondition 设置任务完成条件,当满足条件时,任务状态为完成
|
||||||
|
// - 任务条件值需要变更时可通过 Task.AssignConditionValueAndRefresh 方法变更
|
||||||
|
// - 当多次设置该选项时,后面的设置会覆盖之前的设置
|
||||||
|
func WithCondition(condition Condition) Option {
|
||||||
|
return func(task *Task) {
|
||||||
|
if condition == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if task.Cond == nil {
|
||||||
|
task.Cond = condition
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range condition {
|
||||||
|
task.Cond[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCounter 设置任务计数器,当计数器达到要求时,任务状态为完成
|
||||||
|
// - 一些场景下,任务计数器可能会溢出,此时可通过 WithOverflowCounter 设置可溢出的任务计数器
|
||||||
|
// - 当多次设置该选项时,后面的设置会覆盖之前的设置
|
||||||
|
// - 如果需要初始化计数器的值,可通过 initCount 参数设置
|
||||||
|
func WithCounter(counter int64, initCount ...int64) Option {
|
||||||
|
return func(task *Task) {
|
||||||
|
task.Counter = counter
|
||||||
|
if len(initCount) > 0 {
|
||||||
|
task.CurrCount = initCount[0]
|
||||||
|
if task.CurrCount < 0 {
|
||||||
|
task.CurrCount = 0
|
||||||
|
} else if task.CurrCount > task.Counter {
|
||||||
|
task.CurrCount = task.Counter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverflowCounter 设置可溢出的任务计数器,当计数器达到要求时,任务状态为完成
|
||||||
|
// - 当多次设置该选项时,后面的设置会覆盖之前的设置
|
||||||
|
// - 如果需要初始化计数器的值,可通过 initCount 参数设置
|
||||||
|
func WithOverflowCounter(counter int64, initCount ...int64) Option {
|
||||||
|
return func(task *Task) {
|
||||||
|
task.Counter = counter
|
||||||
|
task.CurrOverflow = true
|
||||||
|
if len(initCount) > 0 {
|
||||||
|
task.CurrCount = initCount[0]
|
||||||
|
if task.CurrCount < 0 {
|
||||||
|
task.CurrCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeadline 设置任务截止时间,超过截至时间并且任务未完成时,任务状态为失败
|
||||||
|
func WithDeadline(deadline time.Time) Option {
|
||||||
|
return func(task *Task) {
|
||||||
|
task.Deadline = deadline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLimitedDuration 设置任务限时,超过限时时间并且任务未完成时,任务状态为失败
|
||||||
|
func WithLimitedDuration(start time.Time, duration time.Duration) Option {
|
||||||
|
return func(task *Task) {
|
||||||
|
task.StartTime = start
|
||||||
|
task.LimitedDuration = duration
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusAccept Status = iota + 1 // 已接受
|
||||||
|
StatusFailed // 已失败
|
||||||
|
StatusComplete // 已完成
|
||||||
|
StatusReward // 已领取奖励
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
statusFormat = map[Status]string{
|
||||||
|
StatusAccept: "Accept",
|
||||||
|
StatusComplete: "Complete",
|
||||||
|
StatusReward: "Reward",
|
||||||
|
StatusFailed: "Failed",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status byte
|
||||||
|
|
||||||
|
func (slf Status) String() string {
|
||||||
|
return statusFormat[slf]
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTask 生成任务
|
||||||
|
func NewTask(options ...Option) *Task {
|
||||||
|
task := new(Task)
|
||||||
|
for _, option := range options {
|
||||||
|
option(task)
|
||||||
|
}
|
||||||
|
return task.refreshTaskStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task 是对任务信息进行描述和处理的结构体
|
||||||
|
type Task struct {
|
||||||
|
Type string `json:"type,omitempty"` // 任务类型
|
||||||
|
Status Status `json:"status,omitempty"` // 任务状态
|
||||||
|
Cond Condition `json:"cond,omitempty"` // 任务条件
|
||||||
|
CondValue map[any]any `json:"cond_value,omitempty"` // 任务条件值
|
||||||
|
Counter int64 `json:"counter,omitempty"` // 任务要求计数器
|
||||||
|
CurrCount int64 `json:"curr_count,omitempty"` // 任务当前计数
|
||||||
|
CurrOverflow bool `json:"curr_overflow,omitempty"` // 任务当前计数是否允许溢出
|
||||||
|
Deadline time.Time `json:"deadline,omitempty"` // 任务截止时间
|
||||||
|
StartTime time.Time `json:"start_time,omitempty"` // 任务开始时间
|
||||||
|
LimitedDuration time.Duration `json:"limited_duration,omitempty"` // 任务限时
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsComplete 判断任务是否已完成
|
||||||
|
func (slf *Task) IsComplete() bool {
|
||||||
|
return slf.Status == StatusComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFailed 判断任务是否已失败
|
||||||
|
func (slf *Task) IsFailed() bool {
|
||||||
|
return slf.Status == StatusFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReward 判断任务是否已领取奖励
|
||||||
|
func (slf *Task) IsReward() bool {
|
||||||
|
return slf.Status == StatusReward
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveReward 领取任务奖励,当任务状态为已完成时,才能领取奖励,此时返回 true,并且任务状态变更为已领取奖励
|
||||||
|
func (slf *Task) ReceiveReward() bool {
|
||||||
|
if slf.Status != StatusComplete {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
slf.Status = StatusReward
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementCounter 增加计数器的值,当 incr 为负数时,计数器的值不会发生变化
|
||||||
|
// - 如果需要溢出计数器,可通过 WithOverflowCounter 设置可溢出的任务计数器
|
||||||
|
func (slf *Task) IncrementCounter(incr int64) *Task {
|
||||||
|
if incr < 0 {
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
slf.CurrCount += incr
|
||||||
|
if !slf.CurrOverflow && slf.CurrCount > slf.Counter {
|
||||||
|
slf.CurrCount = slf.Counter
|
||||||
|
}
|
||||||
|
return slf.refreshTaskStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecrementCounter 减少计数器的值,当 decr 为负数时,计数器的值不会发生变化
|
||||||
|
func (slf *Task) DecrementCounter(decr int64) *Task {
|
||||||
|
if decr < 0 {
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
slf.CurrCount -= decr
|
||||||
|
if slf.CurrCount < 0 {
|
||||||
|
slf.CurrCount = 0
|
||||||
|
}
|
||||||
|
return slf.refreshTaskStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignConditionValueAndRefresh 分配条件值并刷新任务状态
|
||||||
|
func (slf *Task) AssignConditionValueAndRefresh(key, value any) *Task {
|
||||||
|
if slf.Cond == nil {
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
if _, exist := slf.Cond[key]; !exist {
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
if slf.CondValue == nil {
|
||||||
|
slf.CondValue = make(map[any]any)
|
||||||
|
}
|
||||||
|
slf.CondValue[key] = value
|
||||||
|
return slf.refreshTaskStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignConditionValueAndRefreshByCondition 分配条件值并刷新任务状态
|
||||||
|
func (slf *Task) AssignConditionValueAndRefreshByCondition(condition Condition) *Task {
|
||||||
|
if slf.Cond == nil {
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
if slf.CondValue == nil {
|
||||||
|
slf.CondValue = make(map[any]any)
|
||||||
|
}
|
||||||
|
for k, v := range condition {
|
||||||
|
if _, exist := slf.Cond[k]; !exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slf.CondValue[k] = v
|
||||||
|
}
|
||||||
|
return slf.refreshTaskStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetStatus 重置任务状态
|
||||||
|
// - 该函数会将任务状态重置为已接受状态后,再刷新任务状态
|
||||||
|
// - 当任务条件变更,例如任务计数要求为 10,已经完成的情况下,将任务计数要求变更为 5 或 20,此时任务状态由于是已完成或已领取状态,不会自动刷新,需要调用该函数刷新任务状态
|
||||||
|
func (slf *Task) ResetStatus() *Task {
|
||||||
|
slf.Status = StatusAccept
|
||||||
|
return slf.refreshTaskStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshTaskStatus 刷新任务状态
|
||||||
|
func (slf *Task) refreshTaskStatus() *Task {
|
||||||
|
curr := time.Now()
|
||||||
|
if (!slf.StartTime.IsZero() && curr.Before(slf.StartTime)) || (!slf.Deadline.IsZero() && curr.After(slf.Deadline)) || slf.Status >= StatusComplete {
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
slf.Status = StatusComplete
|
||||||
|
|
||||||
|
if slf.Counter > 0 && slf.CurrCount < slf.Counter {
|
||||||
|
slf.Status = StatusAccept
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
if slf.Cond != nil {
|
||||||
|
for k, v := range slf.Cond {
|
||||||
|
if v != slf.CondValue[k] {
|
||||||
|
slf.Status = StatusAccept
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !slf.Deadline.IsZero() && slf.Status == StatusAccept {
|
||||||
|
if slf.Deadline.After(curr) {
|
||||||
|
slf.Status = StatusFailed
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if slf.LimitedDuration > 0 && slf.Status == StatusAccept {
|
||||||
|
if curr.Sub(slf.StartTime) > slf.LimitedDuration {
|
||||||
|
slf.Status = StatusFailed
|
||||||
|
return slf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slf
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
tasks map[string][]*Task
|
||||||
|
}
|
||||||
|
|
||||||
|
type Monster struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCond(t *testing.T) {
|
||||||
|
task := NewTask(WithType("T"), WithCounter(5), WithCondition(Cond("N", 5).Cond("M", 10)))
|
||||||
|
task.AssignConditionValueAndRefresh("N", 5)
|
||||||
|
task.AssignConditionValueAndRefresh("M", 10)
|
||||||
|
|
||||||
|
RegisterRefreshTaskCounterEvent[*Player](task.Type, func(taskType string, trigger *Player, count int64) {
|
||||||
|
fmt.Println("Player", count)
|
||||||
|
for _, t := range trigger.tasks[taskType] {
|
||||||
|
fmt.Println(t.CurrCount, t.IncrementCounter(count).Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
RegisterRefreshTaskConditionEvent[*Player](task.Type, func(taskType string, trigger *Player, condition Condition) {
|
||||||
|
fmt.Println("Player", condition)
|
||||||
|
for _, t := range trigger.tasks[taskType] {
|
||||||
|
fmt.Println(t.CurrCount, t.AssignConditionValueAndRefresh("N", 5).Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
RegisterRefreshTaskCounterEvent[*Monster](task.Type, func(taskType string, trigger *Monster, count int64) {
|
||||||
|
fmt.Println("Monster", count)
|
||||||
|
})
|
||||||
|
|
||||||
|
player := &Player{
|
||||||
|
tasks: map[string][]*Task{
|
||||||
|
task.Type: {task},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, player, 1)
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, player, 2)
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, player, 3)
|
||||||
|
OnRefreshTaskCounterEvent(task.Type, new(Monster), 3)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
module github.com/kercylan98/minotaur
|
||||||
|
|
||||||
|
go 1.22.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108
|
||||||
|
github.com/alphadose/haxmap v1.3.1
|
||||||
|
github.com/gin-contrib/pprof v1.4.0
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/go-resty/resty/v2 v2.11.0
|
||||||
|
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75
|
||||||
|
github.com/gorilla/websocket v1.5.1
|
||||||
|
github.com/json-iterator/go v1.1.12
|
||||||
|
github.com/panjf2000/ants/v2 v2.9.0
|
||||||
|
github.com/panjf2000/gnet v1.6.7
|
||||||
|
github.com/panjf2000/gnet/v2 v2.3.6
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/smartystreets/goconvey v1.8.1
|
||||||
|
github.com/sony/sonyflake v1.2.0
|
||||||
|
github.com/spf13/cobra v1.8.0
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
github.com/tealeg/xlsx v1.0.5
|
||||||
|
github.com/tidwall/gjson v1.17.0
|
||||||
|
github.com/xtaci/kcp-go/v5 v5.6.7
|
||||||
|
go.uber.org/atomic v1.11.0
|
||||||
|
golang.org/x/crypto v0.18.0
|
||||||
|
google.golang.org/grpc v1.60.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/gopkg v0.0.0-20240315062850-21fc7a1671a8 // indirect
|
||||||
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
|
github.com/gobwas/ws v1.3.2 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
|
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/smarty/assertions v1.15.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/templexxx/cpu v0.1.0 // indirect
|
||||||
|
github.com/templexxx/xorsimd v0.4.2 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect
|
||||||
|
golang.org/x/net v0.19.0 // indirect
|
||||||
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
|
golang.org/x/term v0.16.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,353 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108 h1:iPugyBI7oFtbDZXC4dnY093M1kZx6k/95sen92gafbY=
|
||||||
|
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108/go.mod h1:WAMLHwunr1hi3u7OjGV6/VWG9QbdMhGpEKjROiSFd10=
|
||||||
|
github.com/alphadose/haxmap v1.3.1 h1:KmZh75duO1tC8pt3LmUwoTYiZ9sh4K52FX8p7/yrlqU=
|
||||||
|
github.com/alphadose/haxmap v1.3.1/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
|
||||||
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
|
github.com/bytedance/gopkg v0.0.0-20240315062850-21fc7a1671a8 h1:8LX2T6XzOOPvVMS8RH0sY4+QFmO5XyFUnrmwVbtD13k=
|
||||||
|
github.com/bytedance/gopkg v0.0.0-20240315062850-21fc7a1671a8/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
|
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
|
||||||
|
github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
|
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||||
|
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||||
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
|
||||||
|
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
|
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY=
|
||||||
|
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA=
|
||||||
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
|
||||||
|
github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/panjf2000/ants/v2 v2.4.7/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
||||||
|
github.com/panjf2000/ants/v2 v2.9.0 h1:SztCLkVxBRigbg+vt0S5QvF5vxAbxbKt09/YfAJ0tEo=
|
||||||
|
github.com/panjf2000/ants/v2 v2.9.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
|
||||||
|
github.com/panjf2000/gnet v1.6.7 h1:zv1k6kw80sG5ZQrLpbbFDheNCm50zm3z2e3ck5GwMOM=
|
||||||
|
github.com/panjf2000/gnet v1.6.7/go.mod h1:KcOU7QsCaCBjeD5kyshBIamG3d9kAQtlob4Y0v0E+sc=
|
||||||
|
github.com/panjf2000/gnet/v2 v2.3.6 h1:BUHjMPJaNO8N5rQZmhKce9/Iu2ryeMjhKPEOi+ecisQ=
|
||||||
|
github.com/panjf2000/gnet/v2 v2.3.6/go.mod h1:R+X5M5YBpOGMVP/92OJ02P35SbmoHjiL7GnaBhht6GE=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||||
|
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||||
|
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||||
|
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||||
|
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
||||||
|
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||||
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||||
|
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||||
|
github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
|
||||||
|
github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
|
github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
|
||||||
|
github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
|
||||||
|
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||||
|
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/xtaci/kcp-go/v5 v5.6.7 h1:7+rnxNFIsjEwTXQk4cSZpXM4pO0hqtpwE1UFFoJBffA=
|
||||||
|
github.com/xtaci/kcp-go/v5 v5.6.7/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
|
||||||
|
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||||
|
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -0,0 +1,3 @@
|
||||||
|
@echo off
|
||||||
|
echo Open: http://localhost:9998/pkg/github.com/kercylan98/minotaur/
|
||||||
|
godoc -http=:9998 -play
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
echo Open: http://localhost:9998/pkg/github.com/kercylan98/minotaur/
|
||||||
|
godoc -http=:9998 -play
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Modular
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 包级函数定义
|
||||||
|
|
||||||
|
|函数名称|描述
|
||||||
|
|:--|:--
|
||||||
|
|[Run](#Run)|运行模块化应用程序
|
||||||
|
|[RegisterServices](#RegisterServices)|注册服务
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`INTERFACE`|[Block](#struct_Block)|标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数
|
||||||
|
|`INTERFACE`|[Service](#struct_Service)|模块化服务接口,所有的服务均需要实现该接口,在服务的生命周期内发生任何错误均应通过 panic 阻止服务继续运行
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
#### func Run()
|
||||||
|
<span id="Run"></span>
|
||||||
|
> 运行模块化应用程序
|
||||||
|
|
||||||
|
***
|
||||||
|
#### func RegisterServices(s ...Service)
|
||||||
|
<span id="RegisterServices"></span>
|
||||||
|
> 注册服务
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Block"></span>
|
||||||
|
### Block `INTERFACE`
|
||||||
|
标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数
|
||||||
|
|
||||||
|
该接口适用于 Http 服务、WebSocket 服务等需要阻塞进程的服务。需要注意的是, OnBlock 的执行不能保证按照 Service 的注册顺序执行
|
||||||
|
```go
|
||||||
|
type Block interface {
|
||||||
|
Service
|
||||||
|
OnBlock()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Service"></span>
|
||||||
|
### Service `INTERFACE`
|
||||||
|
模块化服务接口,所有的服务均需要实现该接口,在服务的生命周期内发生任何错误均应通过 panic 阻止服务继续运行
|
||||||
|
- 生命周期示例: OnInit -> OnPreload -> OnMount
|
||||||
|
|
||||||
|
在 Golang 中,包与包之间互相引用会导致循环依赖,因此在模块化应用程序中,所有的服务均不应该直接引用其他服务。
|
||||||
|
|
||||||
|
服务应该在 OnInit 阶段将不依赖其他服务的内容初始化完成,并且如果服务需要暴露给其他服务调用,那么也应该在 OnInit 阶段完成对外暴露。
|
||||||
|
- 暴露方式可参考 modular/example
|
||||||
|
|
||||||
|
在 OnPreload 阶段,服务应该完成对其依赖服务的依赖注入,最终在 OnMount 阶段完成对服务功能的定义、路由的声明等。
|
||||||
|
```go
|
||||||
|
type Service interface {
|
||||||
|
OnInit()
|
||||||
|
OnPreload()
|
||||||
|
OnMount()
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,10 @@
|
||||||
|
package modular
|
||||||
|
|
||||||
|
// Block 标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数
|
||||||
|
//
|
||||||
|
// 该接口适用于 Http 服务、WebSocket 服务等需要阻塞进程的服务。需要注意的是, OnBlock 的执行不能保证按照 Service 的注册顺序执行
|
||||||
|
type Block interface {
|
||||||
|
Service
|
||||||
|
// OnBlock 阻塞进程
|
||||||
|
OnBlock()
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package modular
|
||||||
|
|
||||||
|
// Dimension 维度接口
|
||||||
|
// - 维度与服务的区别在于,维度是对非全局性的服务进行抽象,例如:依赖特定游戏房间的局内玩家管理服务
|
||||||
|
type Dimension[Owner any] interface {
|
||||||
|
// OnInit 服务初始化阶段,该阶段不应该依赖其他任何服务
|
||||||
|
OnInit(owner Owner) error
|
||||||
|
|
||||||
|
// OnPreload 预加载阶段,在进入该阶段时,所有服务已经初始化完成,可在该阶段注入其他服务的依赖
|
||||||
|
OnPreload() error
|
||||||
|
|
||||||
|
// OnMount 挂载阶段,该阶段所有服务本身及依赖的服务都已经初始化完成,可在该阶段进行服务功能的定义
|
||||||
|
OnMount() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunDimensions 运行维度
|
||||||
|
func RunDimensions[Owner any](owner Owner, dimensions ...Dimension[Owner]) error {
|
||||||
|
// OnInit
|
||||||
|
for _, dimension := range dimensions {
|
||||||
|
if err := dimension.OnInit(owner); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnPreload
|
||||||
|
for _, dimension := range dimensions {
|
||||||
|
if err := dimension.OnPreload(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMount
|
||||||
|
for _, dimension := range dimensions {
|
||||||
|
if err := dimension.OnMount(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Main
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,4 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
type Events struct {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/modular/example/internal/dimension/dimensions/exposes"
|
||||||
|
|
||||||
|
type Room struct {
|
||||||
|
RoomId int64
|
||||||
|
*Events
|
||||||
|
exposes.VisitorsExpose
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package dimension
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/modular"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/dimension/core"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/dimension/dimensions/dimensions/visitors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(roomId int64) error {
|
||||||
|
visitorsDimension := new(visitors.Dimension)
|
||||||
|
|
||||||
|
return modular.RunDimensions(&core.Room{
|
||||||
|
RoomId: roomId,
|
||||||
|
Events: &core.Events{},
|
||||||
|
|
||||||
|
VisitorsExpose: visitorsDimension,
|
||||||
|
},
|
||||||
|
visitorsDimension,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package visitors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/dimension/core"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/dimension/dimensions/exposes"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/dimension/dimensions/models"
|
||||||
|
"github.com/kercylan98/minotaur/utils/collection"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dimension struct {
|
||||||
|
*core.Room // 房间 Id
|
||||||
|
visitors map[string]*models.VisitorsMember // 所有访客
|
||||||
|
visitorIds []string // 所有访客 OpenId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) OnInit(owner *core.Room) error {
|
||||||
|
exposes.Visitors = d
|
||||||
|
d.Room = owner
|
||||||
|
d.visitors = make(map[string]*models.VisitorsMember)
|
||||||
|
fmt.Println("visitors dimension initialized")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) OnPreload() error {
|
||||||
|
fmt.Println("visitors dimension preloaded")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) OnMount() error {
|
||||||
|
fmt.Println("visitors dimension mounted")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) Count() int {
|
||||||
|
return len(d.visitors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) OpenIds() []string {
|
||||||
|
return d.visitorIds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) Has(openId string) bool {
|
||||||
|
return collection.KeyInMap(d.visitors, openId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) Del(openId string) {
|
||||||
|
member := d.Get(openId)
|
||||||
|
if member == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(d.visitors, openId)
|
||||||
|
collection.DropSliceByIndices(&d.visitorIds, member.OpenIdIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) Get(openId string) *models.VisitorsMember {
|
||||||
|
return d.visitors[openId]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dimension) Add(member *models.VisitorsMember) {
|
||||||
|
member.OpenIdIdx = len(d.visitorIds)
|
||||||
|
d.visitorIds = append(d.visitorIds, member.OpenId)
|
||||||
|
d.visitors[member.OpenId] = member
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package exposes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/dimension/dimensions/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Visitors VisitorsExpose
|
||||||
|
|
||||||
|
type VisitorsExpose interface {
|
||||||
|
// Count 访客数量
|
||||||
|
Count() int
|
||||||
|
|
||||||
|
// OpenIds 访客 OpenId 列表
|
||||||
|
OpenIds() []string
|
||||||
|
|
||||||
|
// Has 是否存在指定 OpenId 的访客
|
||||||
|
Has(openId string) bool
|
||||||
|
|
||||||
|
// Del 删除指定 OpenId 的访客
|
||||||
|
Del(openId string)
|
||||||
|
|
||||||
|
// Get 获取指定 OpenId 的访客
|
||||||
|
Get(openId string) *models.VisitorsMember
|
||||||
|
|
||||||
|
// Add 添加访客
|
||||||
|
Add(member *models.VisitorsMember)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
// VisitorsMember 访客成员
|
||||||
|
type VisitorsMember struct {
|
||||||
|
OpenId string // 访客成员 OpenId
|
||||||
|
OpenIdIdx int // 访客成员 OpenId 索引
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Expose
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`INTERFACE`|[Attack](#struct_Attack)|暂无描述...
|
||||||
|
|`INTERFACE`|[Login](#struct_Login)|暂无描述...
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
<span id="struct_Attack"></span>
|
||||||
|
### Attack `INTERFACE`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Attack interface {
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Login"></span>
|
||||||
|
### Login `INTERFACE`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Login interface {
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,7 @@
|
||||||
|
package expose
|
||||||
|
|
||||||
|
var AttackExpose Attack
|
||||||
|
|
||||||
|
type Attack interface {
|
||||||
|
Name() string
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package expose
|
||||||
|
|
||||||
|
var LoginExpose Login
|
||||||
|
|
||||||
|
type Login interface {
|
||||||
|
Name() string
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Attack
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[Service](#struct_Service)|暂无描述...
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
<span id="struct_Service"></span>
|
||||||
|
### Service `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Service struct {
|
||||||
|
Login expose.Login
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Service_OnInit"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnInit()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_OnPreload"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnPreload()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_OnMount"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnMount()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_Name"></span>
|
||||||
|
|
||||||
|
#### func (*Service) Name() string
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,28 @@
|
||||||
|
package attack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/service/expose"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Login expose.Login
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Service) OnInit() {
|
||||||
|
expose.AttackExpose = a
|
||||||
|
a.name = "attack"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Service) OnPreload() {
|
||||||
|
a.Login = expose.LoginExpose
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Service) OnMount() {
|
||||||
|
fmt.Println("attack service mounted, call", a.Login.Name(), "service")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Service) Name() string {
|
||||||
|
return a.name
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Login
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[Service](#struct_Service)|暂无描述...
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
<span id="struct_Service"></span>
|
||||||
|
### Service `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Service struct {
|
||||||
|
Attack expose.Attack
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Service_OnInit"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnInit()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_OnPreload"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnPreload()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_OnMount"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnMount()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_Name"></span>
|
||||||
|
|
||||||
|
#### func (*Service) Name() string
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,28 @@
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/service/expose"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Attack expose.Attack
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Service) OnInit() {
|
||||||
|
expose.LoginExpose = l
|
||||||
|
l.name = "login"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Service) OnPreload() {
|
||||||
|
l.Attack = expose.AttackExpose
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Service) OnMount() {
|
||||||
|
fmt.Println("attack service mounted, call", l.Attack.Name(), "service")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Service) Name() string {
|
||||||
|
return l.name
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Server
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||||
|

|
||||||
|
|
||||||
|
暂无介绍...
|
||||||
|
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||||
|
<details>
|
||||||
|
<summary>展开 / 折叠目录导航</summary>
|
||||||
|
|
||||||
|
|
||||||
|
> 类型定义
|
||||||
|
|
||||||
|
|类型|名称|描述
|
||||||
|
|:--|:--|:--
|
||||||
|
|`STRUCT`|[Service](#struct_Service)|暂无描述...
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
***
|
||||||
|
## 详情信息
|
||||||
|
<span id="struct_Service"></span>
|
||||||
|
### Service `STRUCT`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Service struct {
|
||||||
|
srv *server.Server
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<span id="struct_Service_OnInit"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnInit()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_OnPreload"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnPreload()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_OnMount"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnMount()
|
||||||
|
|
||||||
|
***
|
||||||
|
<span id="struct_Service_OnBlock"></span>
|
||||||
|
|
||||||
|
#### func (*Service) OnBlock()
|
||||||
|
|
||||||
|
***
|
|
@ -0,0 +1,28 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/server"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
srv *server.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) OnInit() {
|
||||||
|
s.srv = server.New(server.NetworkNone, server.WithLimitLife(time.Second*3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) OnPreload() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) OnMount() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) OnBlock() {
|
||||||
|
if err := s.srv.RunNone(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/modular"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/dimension"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/service/services/attack"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/service/services/login"
|
||||||
|
"github.com/kercylan98/minotaur/modular/example/internal/service/services/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
modular.RegisterServices(
|
||||||
|
new(attack.Service),
|
||||||
|
new(server.Service),
|
||||||
|
new(login.Service),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := dimension.New(1) // generate a room
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modular.Run()
|
||||||
|
}
|