perf: 使用数组代替map优化元素计算性能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
This commit is contained in:
@@ -42,15 +42,15 @@ const (
|
||||
maxMatrixSize = 227 // 矩阵维度(覆盖最大属性ID 226)
|
||||
)
|
||||
|
||||
// 合法单属性ID集合(快速校验)
|
||||
var validSingleElementIDs = map[int]bool{
|
||||
// 合法单属性ID集合(按ID直接索引,避免运行时 map 查找)
|
||||
var validSingleElementIDs = [maxMatrixSize]bool{
|
||||
1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true,
|
||||
11: true, 12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true,
|
||||
221: true, 222: true, 223: true, 224: true, 225: true, 226: true,
|
||||
}
|
||||
|
||||
// 元素名称映射(全属性对应,便于日志输出)
|
||||
var elementNameMap = map[ElementType]string{
|
||||
// 元素名称映射(按ID直接索引,便于日志输出)
|
||||
var elementNameMap = [maxMatrixSize]string{
|
||||
ElementTypeGrass: "GRASS",
|
||||
ElementTypeWater: "WATER",
|
||||
ElementTypeFire: "FIRE",
|
||||
@@ -198,46 +198,55 @@ type ElementCombination struct {
|
||||
ID int // 组合唯一ID
|
||||
}
|
||||
|
||||
// 全局预加载资源(程序启动时init初始化,运行时直接使用)
|
||||
// 全局预加载资源(程序启动时初始化,运行时只读)
|
||||
var (
|
||||
// 元素组合池:key=组合ID,value=组合实例(预加载所有合法组合)
|
||||
elementCombinationPool = make(map[int]*ElementCombination, 150) // 128双+26单=154,预分配足够容量
|
||||
// 单属性克制矩阵(预初始化所有特殊克制关系,默认1.0)
|
||||
validCombinationIDs [maxMatrixSize]bool
|
||||
elementCombinationPool [maxMatrixSize]ElementCombination
|
||||
dualElementSecondaryPool [maxMatrixSize]ElementType
|
||||
matrix [maxMatrixSize][maxMatrixSize]float64
|
||||
Calculator *ElementCalculator
|
||||
)
|
||||
|
||||
// init 预加载所有资源(程序启动时执行一次,无并发问题)
|
||||
func init() {
|
||||
// 1. 初始化单属性克制矩阵
|
||||
initFullTableMatrix()
|
||||
initElementCombinationPool()
|
||||
Calculator = NewElementCalculator()
|
||||
}
|
||||
|
||||
// 2. 预加载所有单属性组合
|
||||
for id := range validSingleElementIDs {
|
||||
combo := &ElementCombination{
|
||||
func initElementCombinationPool() {
|
||||
for id, valid := range validSingleElementIDs {
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
validCombinationIDs[id] = true
|
||||
elementCombinationPool[id] = ElementCombination{
|
||||
Primary: ElementType(id),
|
||||
Secondary: nil,
|
||||
ID: id,
|
||||
}
|
||||
elementCombinationPool[id] = combo
|
||||
}
|
||||
|
||||
// 3. 预加载所有双属性组合
|
||||
for dualID, atts := range dualElementMap {
|
||||
primaryID, secondaryID := atts[0], atts[1]
|
||||
// 按ID升序排序,保证组合一致性
|
||||
primary, secondary := ElementType(primaryID), ElementType(secondaryID)
|
||||
if primary > secondary {
|
||||
primary, secondary = secondary, primary
|
||||
}
|
||||
combo := &ElementCombination{
|
||||
|
||||
dualElementSecondaryPool[dualID] = secondary
|
||||
validCombinationIDs[dualID] = true
|
||||
elementCombinationPool[dualID] = ElementCombination{
|
||||
Primary: primary,
|
||||
Secondary: &secondary,
|
||||
Secondary: &dualElementSecondaryPool[dualID],
|
||||
ID: dualID,
|
||||
}
|
||||
elementCombinationPool[dualID] = combo
|
||||
}
|
||||
}
|
||||
|
||||
func isValidCombinationID(id int) bool {
|
||||
return id > 0 && id < maxMatrixSize && validCombinationIDs[id]
|
||||
}
|
||||
|
||||
// IsDual 判断是否为双属性
|
||||
func (ec *ElementCombination) IsDual() bool {
|
||||
return ec.Secondary != nil
|
||||
@@ -245,84 +254,82 @@ func (ec *ElementCombination) IsDual() bool {
|
||||
|
||||
// Elements 获取所有属性列表
|
||||
func (ec *ElementCombination) Elements() []ElementType {
|
||||
if ec.IsDual() {
|
||||
return []ElementType{ec.Primary, *ec.Secondary}
|
||||
if secondary := ec.Secondary; secondary != nil {
|
||||
return []ElementType{ec.Primary, *secondary}
|
||||
}
|
||||
return []ElementType{ec.Primary}
|
||||
}
|
||||
|
||||
// String 友好格式化输出
|
||||
func (ec *ElementCombination) String() string {
|
||||
primaryName := elementNameMap[ec.Primary]
|
||||
if !ec.IsDual() {
|
||||
return fmt.Sprintf("(%s)", primaryName)
|
||||
if secondary := ec.Secondary; secondary != nil {
|
||||
return fmt.Sprintf("(%s, %s)", elementNameMap[ec.Primary], elementNameMap[*secondary])
|
||||
}
|
||||
return fmt.Sprintf("(%s, %s)", primaryName, elementNameMap[*ec.Secondary])
|
||||
return fmt.Sprintf("(%s)", elementNameMap[ec.Primary])
|
||||
}
|
||||
|
||||
// ElementCalculator 无锁元素克制计算器(依赖预加载资源)
|
||||
// ElementCalculator 无锁元素克制计算器(所有倍数在初始化阶段预计算)
|
||||
type ElementCalculator struct {
|
||||
offensiveCache map[string]float64 // 攻击克制缓存(运行时填充,无并发写)
|
||||
offensiveTable [maxMatrixSize][maxMatrixSize]float64
|
||||
}
|
||||
|
||||
// NewElementCalculator 创建计算器实例(仅初始化缓存)
|
||||
// NewElementCalculator 创建计算器实例(构建只读查表缓存)
|
||||
func NewElementCalculator() *ElementCalculator {
|
||||
return &ElementCalculator{
|
||||
offensiveCache: make(map[string]float64, 4096), // 预分配大容量缓存
|
||||
c := &ElementCalculator{}
|
||||
c.initOffensiveTable()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ElementCalculator) initOffensiveTable() {
|
||||
for attackerID, valid := range validCombinationIDs {
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
attacker := &elementCombinationPool[attackerID]
|
||||
for defenderID, valid := range validCombinationIDs {
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
defender := &elementCombinationPool[defenderID]
|
||||
c.offensiveTable[attackerID][defenderID] = c.calculateMultiplier(attacker, defender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getMatrixValue 直接返回矩阵值(修复核心问题:不再将0转换为1)
|
||||
func (c *ElementCalculator) getMatrixValue(attacker, defender ElementType) float64 {
|
||||
return matrix[attacker][defender] // 矩阵默认已初始化1.0,特殊值直接返回
|
||||
return matrix[attacker][defender]
|
||||
}
|
||||
|
||||
// GetCombination 获取元素组合(直接从预加载池读取)
|
||||
// GetCombination 获取元素组合(直接按ID索引)
|
||||
func (c *ElementCalculator) GetCombination(id int) (*ElementCombination, error) {
|
||||
combo, exists := elementCombinationPool[id]
|
||||
if !exists {
|
||||
if !isValidCombinationID(id) {
|
||||
return nil, fmt.Errorf("invalid element combination ID: %d", id)
|
||||
}
|
||||
return combo, nil
|
||||
return &elementCombinationPool[id], nil
|
||||
}
|
||||
|
||||
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(缓存优先)
|
||||
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(只读查表)
|
||||
func (c *ElementCalculator) GetOffensiveMultiplier(attackerID, defenderID int) (float64, error) {
|
||||
// 1. 获取预加载的组合实例
|
||||
attacker, err := c.GetCombination(attackerID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("attacker invalid: %w", err)
|
||||
if !isValidCombinationID(attackerID) {
|
||||
return 0, fmt.Errorf("attacker invalid: invalid element combination ID: %d", attackerID)
|
||||
}
|
||||
defender, err := c.GetCombination(defenderID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("defender invalid: %w", err)
|
||||
if !isValidCombinationID(defenderID) {
|
||||
return 0, fmt.Errorf("defender invalid: invalid element combination ID: %d", defenderID)
|
||||
}
|
||||
|
||||
// 2. 缓存键(全局唯一)
|
||||
cacheKey := fmt.Sprintf("a%d_d%d", attackerID, defenderID)
|
||||
if val, exists := c.offensiveCache[cacheKey]; exists {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// 3. 核心计算+缓存
|
||||
val := c.calculateMultiplier(attacker, defender)
|
||||
c.offensiveCache[cacheKey] = val
|
||||
return val, nil
|
||||
return c.offensiveTable[attackerID][defenderID], nil
|
||||
}
|
||||
|
||||
// calculateMultiplier 核心克制计算逻辑
|
||||
func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombination) float64 {
|
||||
// 场景1:单→单
|
||||
if !attacker.IsDual() && !defender.IsDual() {
|
||||
return c.getMatrixValue(attacker.Primary, defender.Primary)
|
||||
}
|
||||
|
||||
// 场景2:单→双
|
||||
if !attacker.IsDual() {
|
||||
y1, y2 := defender.Primary, *defender.Secondary
|
||||
m1 := c.getMatrixValue(attacker.Primary, y1)
|
||||
m2 := c.getMatrixValue(attacker.Primary, y2)
|
||||
|
||||
switch {
|
||||
case m1 == 2 && m2 == 2:
|
||||
return 4.0
|
||||
@@ -333,12 +340,10 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
|
||||
}
|
||||
}
|
||||
|
||||
// 场景3:双→单
|
||||
if !defender.IsDual() {
|
||||
return c.calculateDualToSingle(attacker.Primary, *attacker.Secondary, defender.Primary)
|
||||
}
|
||||
|
||||
// 场景4:双→双
|
||||
x1, x2 := attacker.Primary, *attacker.Secondary
|
||||
y1, y2 := defender.Primary, *defender.Secondary
|
||||
coeffY1 := c.calculateDualToSingle(x1, x2, y1)
|
||||
@@ -350,7 +355,6 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
|
||||
func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender ElementType) float64 {
|
||||
k1 := c.getMatrixValue(attacker1, defender)
|
||||
k2 := c.getMatrixValue(attacker2, defender)
|
||||
|
||||
switch {
|
||||
case k1 == 2 && k2 == 2:
|
||||
return 4.0
|
||||
@@ -361,60 +365,49 @@ func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender
|
||||
}
|
||||
}
|
||||
|
||||
var Calculator = NewElementCalculator()
|
||||
|
||||
// TestAllScenarios 全场景测试(验证预加载和计算逻辑)
|
||||
func TestAllScenarios() {
|
||||
|
||||
// 测试1:单→单(草→水)
|
||||
m1, _ := Calculator.GetOffensiveMultiplier(1, 2)
|
||||
fmt.Println("草→水: %.2f(预期2.0)", m1)
|
||||
if math.Abs(m1-2.0) > 0.001 {
|
||||
fmt.Println("测试1失败:实际%.2f", m1)
|
||||
}
|
||||
|
||||
// 测试2:特殊单→单(混沌→虚空)
|
||||
m2, _ := Calculator.GetOffensiveMultiplier(222, 226)
|
||||
fmt.Println("混沌→虚空: %.2f(预期0.0)", m2)
|
||||
if math.Abs(m2-0.0) > 0.001 {
|
||||
fmt.Println("测试2失败:实际%.2f", m2)
|
||||
}
|
||||
|
||||
// 测试3:单→双(火→冰龙(43))
|
||||
m3, _ := Calculator.GetOffensiveMultiplier(3, 43)
|
||||
fmt.Println("火→冰龙: %.2f(预期1.5)", m3)
|
||||
if math.Abs(m3-1.5) > 0.001 {
|
||||
fmt.Println("测试3失败:实际%.2f", m3)
|
||||
}
|
||||
|
||||
// 测试4:双→特殊单(混沌暗影(92)→神灵(223))
|
||||
m4, _ := Calculator.GetOffensiveMultiplier(92, 223)
|
||||
fmt.Println("混沌暗影→神灵: %.2f(预期1.25)", m4)
|
||||
if math.Abs(m4-1.25) > 0.001 {
|
||||
fmt.Println("测试4失败:实际%.2f", m4)
|
||||
}
|
||||
|
||||
// 测试5:双→双(虚空邪灵(113)→混沌远古(98))
|
||||
m5, _ := Calculator.GetOffensiveMultiplier(113, 98)
|
||||
fmt.Println("虚空邪灵→混沌远古: %.2f(预期0.875", m5)
|
||||
if math.Abs(m5-0.875) > 0.001 {
|
||||
fmt.Println("测试5失败:实际%.2f", m5)
|
||||
}
|
||||
|
||||
// 测试6:缓存命中
|
||||
m6, _ := Calculator.GetOffensiveMultiplier(113, 98)
|
||||
if math.Abs(m6-m5) > 0.001 {
|
||||
fmt.Println("测试6失败:缓存未命中")
|
||||
}
|
||||
|
||||
// 测试7:含无效组合(电→地面)
|
||||
m7, _ := Calculator.GetOffensiveMultiplier(5, 7)
|
||||
fmt.Println("电→地面: %.2f(预期0.0)", m7)
|
||||
if math.Abs(m7-0.0) > 0.001 {
|
||||
fmt.Println("测试7失败:实际%.2f", m7)
|
||||
}
|
||||
|
||||
// 测试8:双属性含无效(电战斗→地面)
|
||||
m8, _ := Calculator.GetOffensiveMultiplier(35, 7)
|
||||
fmt.Println("电战斗→地面: %.2f(预期0.25)", m8)
|
||||
if math.Abs(m8-0.25) > 0.001 {
|
||||
|
||||
Reference in New Issue
Block a user