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