fix and enhance aa package

This commit is contained in:
Sky Johnson 2025-07-31 18:14:05 -05:00
parent 1bf001834b
commit 8a568d31bd
4 changed files with 4063 additions and 87 deletions

487
coverage.out Normal file
View File

@ -0,0 +1,487 @@
mode: set
eq2emu/internal/alt_advancement/database.go:9.53,20.16 3 0
eq2emu/internal/alt_advancement/database.go:20.16,22.3 1 0
eq2emu/internal/alt_advancement/database.go:23.2,26.18 3 0
eq2emu/internal/alt_advancement/database.go:26.18,54.17 3 0
eq2emu/internal/alt_advancement/database.go:54.17,56.4 1 0
eq2emu/internal/alt_advancement/database.go:59.3,62.65 2 0
eq2emu/internal/alt_advancement/database.go:62.65,64.24 1 0
eq2emu/internal/alt_advancement/database.go:64.24,66.5 1 0
eq2emu/internal/alt_advancement/database.go:67.4,67.12 1 0
eq2emu/internal/alt_advancement/database.go:70.3,70.16 1 0
eq2emu/internal/alt_advancement/database.go:73.2,73.34 1 0
eq2emu/internal/alt_advancement/database.go:73.34,75.3 1 0
eq2emu/internal/alt_advancement/database.go:78.2,80.22 2 0
eq2emu/internal/alt_advancement/database.go:80.22,82.3 1 0
eq2emu/internal/alt_advancement/database.go:84.2,84.12 1 0
eq2emu/internal/alt_advancement/database.go:88.47,95.16 3 0
eq2emu/internal/alt_advancement/database.go:95.16,97.3 1 0
eq2emu/internal/alt_advancement/database.go:98.2,101.18 3 0
eq2emu/internal/alt_advancement/database.go:101.18,109.17 3 0
eq2emu/internal/alt_advancement/database.go:109.17,111.4 1 0
eq2emu/internal/alt_advancement/database.go:114.3,114.61 1 0
eq2emu/internal/alt_advancement/database.go:114.61,116.24 1 0
eq2emu/internal/alt_advancement/database.go:116.24,118.5 1 0
eq2emu/internal/alt_advancement/database.go:119.4,119.12 1 0
eq2emu/internal/alt_advancement/database.go:122.3,122.16 1 0
eq2emu/internal/alt_advancement/database.go:125.2,125.34 1 0
eq2emu/internal/alt_advancement/database.go:125.34,127.3 1 0
eq2emu/internal/alt_advancement/database.go:129.2,129.22 1 0
eq2emu/internal/alt_advancement/database.go:129.22,131.3 1 0
eq2emu/internal/alt_advancement/database.go:133.2,133.12 1 0
eq2emu/internal/alt_advancement/database.go:137.81,148.16 4 0
eq2emu/internal/alt_advancement/database.go:148.16,150.3 1 0
eq2emu/internal/alt_advancement/database.go:151.2,156.18 3 0
eq2emu/internal/alt_advancement/database.go:156.18,166.17 3 0
eq2emu/internal/alt_advancement/database.go:166.17,168.4 1 0
eq2emu/internal/alt_advancement/database.go:170.3,170.47 1 0
eq2emu/internal/alt_advancement/database.go:170.47,172.4 1 0
eq2emu/internal/alt_advancement/database.go:173.3,173.87 1 0
eq2emu/internal/alt_advancement/database.go:176.2,176.34 1 0
eq2emu/internal/alt_advancement/database.go:176.34,178.3 1 0
eq2emu/internal/alt_advancement/database.go:181.2,181.51 1 0
eq2emu/internal/alt_advancement/database.go:181.51,185.3 3 0
eq2emu/internal/alt_advancement/database.go:188.2,189.16 2 0
eq2emu/internal/alt_advancement/database.go:189.16,191.3 1 0
eq2emu/internal/alt_advancement/database.go:194.2,195.16 2 0
eq2emu/internal/alt_advancement/database.go:195.16,197.3 1 0
eq2emu/internal/alt_advancement/database.go:200.2,202.25 2 0
eq2emu/internal/alt_advancement/database.go:206.99,214.16 3 0
eq2emu/internal/alt_advancement/database.go:214.16,216.3 1 0
eq2emu/internal/alt_advancement/database.go:217.2,219.18 2 0
eq2emu/internal/alt_advancement/database.go:219.18,234.17 4 0
eq2emu/internal/alt_advancement/database.go:234.17,236.4 1 0
eq2emu/internal/alt_advancement/database.go:239.3,239.93 1 0
eq2emu/internal/alt_advancement/database.go:239.93,241.4 1 0
eq2emu/internal/alt_advancement/database.go:242.3,242.89 1 0
eq2emu/internal/alt_advancement/database.go:242.89,244.4 1 0
eq2emu/internal/alt_advancement/database.go:246.3,246.53 1 0
eq2emu/internal/alt_advancement/database.go:249.2,249.19 1 0
eq2emu/internal/alt_advancement/database.go:253.97,270.16 4 0
eq2emu/internal/alt_advancement/database.go:270.16,272.50 1 0
eq2emu/internal/alt_advancement/database.go:272.50,279.4 6 0
eq2emu/internal/alt_advancement/database.go:280.3,280.64 1 0
eq2emu/internal/alt_advancement/database.go:283.2,283.12 1 0
eq2emu/internal/alt_advancement/database.go:287.74,289.32 1 0
eq2emu/internal/alt_advancement/database.go:289.32,295.51 4 0
eq2emu/internal/alt_advancement/database.go:295.51,296.27 1 0
eq2emu/internal/alt_advancement/database.go:296.27,298.5 1 0
eq2emu/internal/alt_advancement/database.go:300.3,303.28 3 0
eq2emu/internal/alt_advancement/database.go:308.72,309.24 1 0
eq2emu/internal/alt_advancement/database.go:309.24,311.3 1 0
eq2emu/internal/alt_advancement/database.go:314.2,315.16 2 0
eq2emu/internal/alt_advancement/database.go:315.16,317.3 1 0
eq2emu/internal/alt_advancement/database.go:318.2,322.16 3 0
eq2emu/internal/alt_advancement/database.go:322.16,324.3 1 0
eq2emu/internal/alt_advancement/database.go:327.2,328.16 2 0
eq2emu/internal/alt_advancement/database.go:328.16,330.3 1 0
eq2emu/internal/alt_advancement/database.go:333.2,334.16 2 0
eq2emu/internal/alt_advancement/database.go:334.16,336.3 1 0
eq2emu/internal/alt_advancement/database.go:339.2,339.35 1 0
eq2emu/internal/alt_advancement/database.go:339.35,341.3 1 0
eq2emu/internal/alt_advancement/database.go:344.2,347.12 3 0
eq2emu/internal/alt_advancement/database.go:351.94,369.2 3 0
eq2emu/internal/alt_advancement/database.go:372.96,375.16 2 0
eq2emu/internal/alt_advancement/database.go:375.16,377.3 1 0
eq2emu/internal/alt_advancement/database.go:380.2,386.50 2 0
eq2emu/internal/alt_advancement/database.go:386.50,397.17 2 0
eq2emu/internal/alt_advancement/database.go:397.17,399.4 1 0
eq2emu/internal/alt_advancement/database.go:402.2,402.12 1 0
eq2emu/internal/alt_advancement/database.go:406.97,409.16 2 0
eq2emu/internal/alt_advancement/database.go:409.16,411.3 1 0
eq2emu/internal/alt_advancement/database.go:414.2,419.49 2 0
eq2emu/internal/alt_advancement/database.go:419.49,421.59 1 0
eq2emu/internal/alt_advancement/database.go:421.59,422.43 1 0
eq2emu/internal/alt_advancement/database.go:422.43,431.19 2 0
eq2emu/internal/alt_advancement/database.go:431.19,433.6 1 0
eq2emu/internal/alt_advancement/database.go:438.2,438.12 1 0
eq2emu/internal/alt_advancement/database.go:442.89,450.16 3 0
eq2emu/internal/alt_advancement/database.go:450.16,452.3 1 0
eq2emu/internal/alt_advancement/database.go:453.2,457.18 3 0
eq2emu/internal/alt_advancement/database.go:457.18,467.17 3 0
eq2emu/internal/alt_advancement/database.go:467.17,469.4 1 0
eq2emu/internal/alt_advancement/database.go:471.3,471.41 1 0
eq2emu/internal/alt_advancement/database.go:471.41,473.4 1 0
eq2emu/internal/alt_advancement/database.go:474.3,474.75 1 0
eq2emu/internal/alt_advancement/database.go:477.2,477.34 1 0
eq2emu/internal/alt_advancement/database.go:477.34,479.3 1 0
eq2emu/internal/alt_advancement/database.go:481.2,481.23 1 0
eq2emu/internal/alt_advancement/database.go:485.65,488.16 2 0
eq2emu/internal/alt_advancement/database.go:488.16,490.3 1 0
eq2emu/internal/alt_advancement/database.go:491.2,500.31 3 0
eq2emu/internal/alt_advancement/database.go:500.31,503.17 3 0
eq2emu/internal/alt_advancement/database.go:503.17,505.4 1 0
eq2emu/internal/alt_advancement/database.go:509.2,509.35 1 0
eq2emu/internal/alt_advancement/database.go:509.35,511.3 1 0
eq2emu/internal/alt_advancement/database.go:513.2,513.12 1 0
eq2emu/internal/alt_advancement/database.go:517.75,523.16 4 0
eq2emu/internal/alt_advancement/database.go:523.16,525.3 1 0
eq2emu/internal/alt_advancement/database.go:526.2,531.16 4 0
eq2emu/internal/alt_advancement/database.go:531.16,533.3 1 0
eq2emu/internal/alt_advancement/database.go:534.2,546.16 4 0
eq2emu/internal/alt_advancement/database.go:546.16,548.3 1 0
eq2emu/internal/alt_advancement/database.go:549.2,552.18 3 0
eq2emu/internal/alt_advancement/database.go:552.18,556.17 4 0
eq2emu/internal/alt_advancement/database.go:556.17,558.4 1 0
eq2emu/internal/alt_advancement/database.go:559.3,559.29 1 0
eq2emu/internal/alt_advancement/database.go:561.2,563.19 2 0
eq2emu/internal/alt_advancement/interfaces.go:191.130,198.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:282.77,287.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:290.54,292.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:295.45,297.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:300.79,302.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:305.70,307.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:310.51,312.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:315.77,317.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:320.60,322.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:325.67,327.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:330.62,332.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:335.69,337.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:340.59,342.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:345.42,347.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:355.57,357.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:360.48,362.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:365.52,367.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:370.45,372.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:375.45,377.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:380.54,382.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:385.44,387.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:390.46,392.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:395.67,397.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:405.57,407.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:410.48,412.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:415.52,417.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:420.48,422.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:425.59,427.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:430.54,432.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:446.53,453.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:456.69,460.46 3 1
eq2emu/internal/alt_advancement/interfaces.go:460.46,463.3 2 1
eq2emu/internal/alt_advancement/interfaces.go:465.2,466.19 2 1
eq2emu/internal/alt_advancement/interfaces.go:470.69,474.39 3 1
eq2emu/internal/alt_advancement/interfaces.go:474.39,476.27 1 0
eq2emu/internal/alt_advancement/interfaces.go:476.27,478.9 2 0
eq2emu/internal/alt_advancement/interfaces.go:482.2,482.34 1 1
eq2emu/internal/alt_advancement/interfaces.go:486.52,491.2 3 1
eq2emu/internal/alt_advancement/interfaces.go:494.82,498.58 3 1
eq2emu/internal/alt_advancement/interfaces.go:498.58,501.3 2 1
eq2emu/internal/alt_advancement/interfaces.go:503.2,504.19 2 1
eq2emu/internal/alt_advancement/interfaces.go:508.87,512.45 3 1
eq2emu/internal/alt_advancement/interfaces.go:512.45,514.33 1 0
eq2emu/internal/alt_advancement/interfaces.go:514.33,516.9 2 0
eq2emu/internal/alt_advancement/interfaces.go:520.2,520.43 1 1
eq2emu/internal/alt_advancement/interfaces.go:524.66,529.2 3 0
eq2emu/internal/alt_advancement/interfaces.go:532.73,536.49 3 1
eq2emu/internal/alt_advancement/interfaces.go:536.49,540.3 3 1
eq2emu/internal/alt_advancement/interfaces.go:542.2,543.19 2 1
eq2emu/internal/alt_advancement/interfaces.go:547.75,551.42 3 1
eq2emu/internal/alt_advancement/interfaces.go:551.42,553.30 1 0
eq2emu/internal/alt_advancement/interfaces.go:553.30,555.9 2 0
eq2emu/internal/alt_advancement/interfaces.go:559.2,560.33 2 1
eq2emu/internal/alt_advancement/interfaces.go:564.58,569.2 3 0
eq2emu/internal/alt_advancement/interfaces.go:572.33,579.2 5 1
eq2emu/internal/alt_advancement/interfaces.go:582.59,594.2 3 1
eq2emu/internal/alt_advancement/interfaces.go:597.51,602.2 3 1
eq2emu/internal/alt_advancement/manager.go:9.54,19.2 1 1
eq2emu/internal/alt_advancement/manager.go:22.36,24.40 1 1
eq2emu/internal/alt_advancement/manager.go:24.40,26.3 1 0
eq2emu/internal/alt_advancement/manager.go:29.2,29.34 1 1
eq2emu/internal/alt_advancement/manager.go:29.34,32.3 2 1
eq2emu/internal/alt_advancement/manager.go:34.2,34.54 1 1
eq2emu/internal/alt_advancement/manager.go:34.54,37.3 2 1
eq2emu/internal/alt_advancement/manager.go:39.2,39.12 1 1
eq2emu/internal/alt_advancement/manager.go:43.35,48.24 3 1
eq2emu/internal/alt_advancement/manager.go:48.24,50.3 1 1
eq2emu/internal/alt_advancement/manager.go:52.2,52.12 1 1
eq2emu/internal/alt_advancement/manager.go:56.39,57.9 1 1
eq2emu/internal/alt_advancement/manager.go:58.21,59.15 1 1
eq2emu/internal/alt_advancement/manager.go:60.10,61.14 1 1
eq2emu/internal/alt_advancement/manager.go:66.41,67.24 1 1
eq2emu/internal/alt_advancement/manager.go:67.24,69.3 1 1
eq2emu/internal/alt_advancement/manager.go:71.2,74.58 2 1
eq2emu/internal/alt_advancement/manager.go:74.58,76.3 1 1
eq2emu/internal/alt_advancement/manager.go:79.2,79.52 1 1
eq2emu/internal/alt_advancement/manager.go:79.52,81.3 1 0
eq2emu/internal/alt_advancement/manager.go:84.2,94.12 8 1
eq2emu/internal/alt_advancement/manager.go:98.43,110.16 7 1
eq2emu/internal/alt_advancement/manager.go:110.16,112.3 1 1
eq2emu/internal/alt_advancement/manager.go:114.2,114.12 1 1
eq2emu/internal/alt_advancement/manager.go:118.78,122.2 1 0
eq2emu/internal/alt_advancement/manager.go:125.90,126.24 1 1
eq2emu/internal/alt_advancement/manager.go:126.24,128.3 1 1
eq2emu/internal/alt_advancement/manager.go:130.2,131.16 2 1
eq2emu/internal/alt_advancement/manager.go:131.16,133.3 1 0
eq2emu/internal/alt_advancement/manager.go:135.2,135.25 1 1
eq2emu/internal/alt_advancement/manager.go:139.60,140.24 1 1
eq2emu/internal/alt_advancement/manager.go:140.24,142.3 1 1
eq2emu/internal/alt_advancement/manager.go:145.2,146.24 2 1
eq2emu/internal/alt_advancement/manager.go:146.24,148.3 1 0
eq2emu/internal/alt_advancement/manager.go:151.2,151.46 1 1
eq2emu/internal/alt_advancement/manager.go:155.82,158.65 2 1
eq2emu/internal/alt_advancement/manager.go:158.65,161.3 2 1
eq2emu/internal/alt_advancement/manager.go:162.2,169.65 4 1
eq2emu/internal/alt_advancement/manager.go:169.65,171.3 1 1
eq2emu/internal/alt_advancement/manager.go:174.2,175.16 2 1
eq2emu/internal/alt_advancement/manager.go:175.16,177.3 1 1
eq2emu/internal/alt_advancement/manager.go:180.2,185.25 3 1
eq2emu/internal/alt_advancement/manager.go:189.89,192.16 2 1
eq2emu/internal/alt_advancement/manager.go:192.16,194.3 1 0
eq2emu/internal/alt_advancement/manager.go:197.2,198.19 2 1
eq2emu/internal/alt_advancement/manager.go:198.19,200.3 1 1
eq2emu/internal/alt_advancement/manager.go:203.2,203.25 1 1
eq2emu/internal/alt_advancement/manager.go:203.25,204.90 1 0
eq2emu/internal/alt_advancement/manager.go:204.90,207.4 2 0
eq2emu/internal/alt_advancement/manager.go:211.2,212.16 2 1
eq2emu/internal/alt_advancement/manager.go:212.16,214.3 1 1
eq2emu/internal/alt_advancement/manager.go:217.2,221.24 3 1
eq2emu/internal/alt_advancement/manager.go:221.24,223.3 1 0
eq2emu/internal/alt_advancement/manager.go:226.2,226.26 1 1
eq2emu/internal/alt_advancement/manager.go:226.26,228.3 1 0
eq2emu/internal/alt_advancement/manager.go:230.2,230.12 1 1
eq2emu/internal/alt_advancement/manager.go:234.70,237.24 2 1
eq2emu/internal/alt_advancement/manager.go:237.24,239.3 1 1
eq2emu/internal/alt_advancement/manager.go:242.2,243.19 2 0
eq2emu/internal/alt_advancement/manager.go:243.19,245.3 1 0
eq2emu/internal/alt_advancement/manager.go:248.2,249.42 2 0
eq2emu/internal/alt_advancement/manager.go:249.42,251.3 1 0
eq2emu/internal/alt_advancement/manager.go:254.2,268.24 9 0
eq2emu/internal/alt_advancement/manager.go:268.24,270.3 1 0
eq2emu/internal/alt_advancement/manager.go:273.2,273.26 1 0
eq2emu/internal/alt_advancement/manager.go:273.26,275.3 1 0
eq2emu/internal/alt_advancement/manager.go:277.2,277.12 1 0
eq2emu/internal/alt_advancement/manager.go:281.96,284.24 2 1
eq2emu/internal/alt_advancement/manager.go:284.24,286.3 1 1
eq2emu/internal/alt_advancement/manager.go:289.2,292.28 3 0
eq2emu/internal/alt_advancement/manager.go:292.28,294.40 1 0
eq2emu/internal/alt_advancement/manager.go:294.40,296.4 1 0
eq2emu/internal/alt_advancement/manager.go:299.2,299.26 1 0
eq2emu/internal/alt_advancement/manager.go:303.81,306.24 2 1
eq2emu/internal/alt_advancement/manager.go:306.24,308.3 1 1
eq2emu/internal/alt_advancement/manager.go:311.2,311.25 1 0
eq2emu/internal/alt_advancement/manager.go:311.25,312.86 1 0
eq2emu/internal/alt_advancement/manager.go:312.86,314.4 1 0
eq2emu/internal/alt_advancement/manager.go:318.2,327.12 7 0
eq2emu/internal/alt_advancement/manager.go:331.92,334.24 2 1
eq2emu/internal/alt_advancement/manager.go:334.24,336.3 1 1
eq2emu/internal/alt_advancement/manager.go:339.2,340.21 2 0
eq2emu/internal/alt_advancement/manager.go:340.21,343.3 2 0
eq2emu/internal/alt_advancement/manager.go:343.8,346.3 2 0
eq2emu/internal/alt_advancement/manager.go:348.2,355.12 5 0
eq2emu/internal/alt_advancement/manager.go:359.86,362.24 2 1
eq2emu/internal/alt_advancement/manager.go:362.24,364.3 1 1
eq2emu/internal/alt_advancement/manager.go:367.2,369.50 3 1
eq2emu/internal/alt_advancement/manager.go:369.50,371.3 1 0
eq2emu/internal/alt_advancement/manager.go:372.2,374.23 2 1
eq2emu/internal/alt_advancement/manager.go:378.90,381.16 2 1
eq2emu/internal/alt_advancement/manager.go:381.16,383.3 1 0
eq2emu/internal/alt_advancement/manager.go:386.2,396.24 9 1
eq2emu/internal/alt_advancement/manager.go:396.24,398.3 1 0
eq2emu/internal/alt_advancement/manager.go:401.2,403.12 2 1
eq2emu/internal/alt_advancement/manager.go:407.82,410.24 2 1
eq2emu/internal/alt_advancement/manager.go:410.24,412.3 1 1
eq2emu/internal/alt_advancement/manager.go:414.2,417.91 3 1
eq2emu/internal/alt_advancement/manager.go:421.67,423.19 2 1
eq2emu/internal/alt_advancement/manager.go:423.19,425.3 1 0
eq2emu/internal/alt_advancement/manager.go:426.2,426.20 1 1
eq2emu/internal/alt_advancement/manager.go:430.77,432.19 2 1
eq2emu/internal/alt_advancement/manager.go:432.19,434.3 1 0
eq2emu/internal/alt_advancement/manager.go:435.2,435.20 1 1
eq2emu/internal/alt_advancement/manager.go:439.75,441.2 1 1
eq2emu/internal/alt_advancement/manager.go:444.77,446.2 1 0
eq2emu/internal/alt_advancement/manager.go:449.55,456.2 4 1
eq2emu/internal/alt_advancement/manager.go:459.79,461.24 2 1
eq2emu/internal/alt_advancement/manager.go:461.24,463.3 1 0
eq2emu/internal/alt_advancement/manager.go:465.2,478.3 3 1
eq2emu/internal/alt_advancement/manager.go:482.62,485.2 2 0
eq2emu/internal/alt_advancement/manager.go:488.50,490.2 1 1
eq2emu/internal/alt_advancement/manager.go:495.49,497.2 1 1
eq2emu/internal/alt_advancement/manager.go:500.64,502.2 1 0
eq2emu/internal/alt_advancement/manager.go:505.62,510.2 3 1
eq2emu/internal/alt_advancement/manager.go:513.58,515.2 1 0
eq2emu/internal/alt_advancement/manager.go:518.55,520.2 1 0
eq2emu/internal/alt_advancement/manager.go:523.56,525.2 1 0
eq2emu/internal/alt_advancement/manager.go:528.46,530.2 1 0
eq2emu/internal/alt_advancement/manager.go:535.71,540.2 3 1
eq2emu/internal/alt_advancement/manager.go:543.115,552.46 4 1
eq2emu/internal/alt_advancement/manager.go:552.46,554.3 1 1
eq2emu/internal/alt_advancement/manager.go:557.2,558.21 2 1
eq2emu/internal/alt_advancement/manager.go:558.21,570.3 2 1
eq2emu/internal/alt_advancement/manager.go:573.2,582.12 7 1
eq2emu/internal/alt_advancement/manager.go:586.93,596.29 1 0
eq2emu/internal/alt_advancement/manager.go:596.29,598.64 2 0
eq2emu/internal/alt_advancement/manager.go:598.64,600.4 1 0
eq2emu/internal/alt_advancement/manager.go:603.2,603.13 1 0
eq2emu/internal/alt_advancement/manager.go:609.40,615.6 4 1
eq2emu/internal/alt_advancement/manager.go:615.6,616.10 1 1
eq2emu/internal/alt_advancement/manager.go:617.19,618.25 1 1
eq2emu/internal/alt_advancement/manager.go:619.22,620.10 1 1
eq2emu/internal/alt_advancement/manager.go:626.37,632.6 4 1
eq2emu/internal/alt_advancement/manager.go:632.6,633.10 1 1
eq2emu/internal/alt_advancement/manager.go:634.19,635.28 1 1
eq2emu/internal/alt_advancement/manager.go:636.22,637.10 1 1
eq2emu/internal/alt_advancement/manager.go:643.41,653.46 7 1
eq2emu/internal/alt_advancement/manager.go:653.46,658.3 4 0
eq2emu/internal/alt_advancement/manager.go:659.2,664.32 4 1
eq2emu/internal/alt_advancement/manager.go:664.32,666.3 1 0
eq2emu/internal/alt_advancement/manager.go:668.2,668.39 1 1
eq2emu/internal/alt_advancement/manager.go:672.44,673.24 1 1
eq2emu/internal/alt_advancement/manager.go:673.24,675.3 1 0
eq2emu/internal/alt_advancement/manager.go:677.2,680.46 3 1
eq2emu/internal/alt_advancement/manager.go:680.46,681.28 1 0
eq2emu/internal/alt_advancement/manager.go:681.28,682.64 1 0
eq2emu/internal/alt_advancement/manager.go:682.64,684.13 2 0
eq2emu/internal/alt_advancement/manager.go:686.4,686.33 1 0
eq2emu/internal/alt_advancement/manager.go:692.57,696.19 3 0
eq2emu/internal/alt_advancement/manager.go:697.27,698.30 1 0
eq2emu/internal/alt_advancement/manager.go:699.25,700.28 1 0
eq2emu/internal/alt_advancement/manager.go:701.23,702.26 1 0
eq2emu/internal/alt_advancement/manager.go:709.72,713.43 3 1
eq2emu/internal/alt_advancement/manager.go:713.43,715.3 1 1
eq2emu/internal/alt_advancement/manager.go:719.46,723.43 3 1
eq2emu/internal/alt_advancement/manager.go:723.43,725.3 1 0
eq2emu/internal/alt_advancement/manager.go:729.93,733.43 3 1
eq2emu/internal/alt_advancement/manager.go:733.43,735.3 1 1
eq2emu/internal/alt_advancement/manager.go:739.109,743.43 3 1
eq2emu/internal/alt_advancement/manager.go:743.43,745.3 1 1
eq2emu/internal/alt_advancement/manager.go:749.111,753.43 3 0
eq2emu/internal/alt_advancement/manager.go:753.43,755.3 1 0
eq2emu/internal/alt_advancement/manager.go:759.97,763.43 3 0
eq2emu/internal/alt_advancement/manager.go:763.43,765.3 1 0
eq2emu/internal/alt_advancement/manager.go:769.96,773.43 3 0
eq2emu/internal/alt_advancement/manager.go:773.43,775.3 1 0
eq2emu/internal/alt_advancement/manager.go:779.100,783.43 3 1
eq2emu/internal/alt_advancement/manager.go:783.43,785.3 1 1
eq2emu/internal/alt_advancement/master_list.go:10.38,19.2 1 1
eq2emu/internal/alt_advancement/master_list.go:22.72,23.17 1 1
eq2emu/internal/alt_advancement/master_list.go:23.17,25.3 1 1
eq2emu/internal/alt_advancement/master_list.go:27.2,27.21 1 1
eq2emu/internal/alt_advancement/master_list.go:27.21,29.3 1 1
eq2emu/internal/alt_advancement/master_list.go:31.2,35.56 3 1
eq2emu/internal/alt_advancement/master_list.go:35.56,37.3 1 1
eq2emu/internal/alt_advancement/master_list.go:39.2,39.54 1 1
eq2emu/internal/alt_advancement/master_list.go:39.54,41.3 1 1
eq2emu/internal/alt_advancement/master_list.go:44.2,51.38 4 1
eq2emu/internal/alt_advancement/master_list.go:51.38,53.3 1 1
eq2emu/internal/alt_advancement/master_list.go:54.2,58.12 3 1
eq2emu/internal/alt_advancement/master_list.go:62.75,66.54 3 1
eq2emu/internal/alt_advancement/master_list.go:66.54,68.3 1 1
eq2emu/internal/alt_advancement/master_list.go:70.2,70.12 1 1
eq2emu/internal/alt_advancement/master_list.go:74.82,78.52 3 1
eq2emu/internal/alt_advancement/master_list.go:78.52,80.3 1 1
eq2emu/internal/alt_advancement/master_list.go:82.2,82.12 1 1
eq2emu/internal/alt_advancement/master_list.go:86.70,90.52 3 1
eq2emu/internal/alt_advancement/master_list.go:90.52,93.29 2 1
eq2emu/internal/alt_advancement/master_list.go:93.29,95.4 1 1
eq2emu/internal/alt_advancement/master_list.go:96.3,96.16 1 1
eq2emu/internal/alt_advancement/master_list.go:99.2,99.28 1 1
eq2emu/internal/alt_advancement/master_list.go:103.72,109.32 4 1
eq2emu/internal/alt_advancement/master_list.go:109.32,111.49 1 1
eq2emu/internal/alt_advancement/master_list.go:111.49,113.4 1 1
eq2emu/internal/alt_advancement/master_list.go:116.2,116.15 1 1
eq2emu/internal/alt_advancement/master_list.go:120.70,126.32 4 1
eq2emu/internal/alt_advancement/master_list.go:126.32,127.27 1 1
eq2emu/internal/alt_advancement/master_list.go:127.27,129.4 1 1
eq2emu/internal/alt_advancement/master_list.go:132.2,132.15 1 1
eq2emu/internal/alt_advancement/master_list.go:136.37,141.2 3 1
eq2emu/internal/alt_advancement/master_list.go:144.56,149.32 4 1
eq2emu/internal/alt_advancement/master_list.go:149.32,151.3 1 1
eq2emu/internal/alt_advancement/master_list.go:153.2,153.15 1 1
eq2emu/internal/alt_advancement/master_list.go:157.51,166.2 7 1
eq2emu/internal/alt_advancement/master_list.go:169.43,173.35 3 1
eq2emu/internal/alt_advancement/master_list.go:173.35,174.56 1 1
eq2emu/internal/alt_advancement/master_list.go:174.56,179.26 3 1
eq2emu/internal/alt_advancement/master_list.go:179.26,181.5 1 1
eq2emu/internal/alt_advancement/master_list.go:182.4,182.28 1 1
eq2emu/internal/alt_advancement/master_list.go:188.46,193.2 3 0
eq2emu/internal/alt_advancement/master_list.go:196.45,201.35 4 1
eq2emu/internal/alt_advancement/master_list.go:201.35,203.3 1 1
eq2emu/internal/alt_advancement/master_list.go:205.2,205.41 1 1
eq2emu/internal/alt_advancement/master_list.go:205.41,207.3 1 1
eq2emu/internal/alt_advancement/master_list.go:209.2,209.15 1 1
eq2emu/internal/alt_advancement/master_list.go:213.51,219.32 4 0
eq2emu/internal/alt_advancement/master_list.go:219.32,220.20 1 0
eq2emu/internal/alt_advancement/master_list.go:220.20,222.4 1 0
eq2emu/internal/alt_advancement/master_list.go:225.3,225.26 1 0
eq2emu/internal/alt_advancement/master_list.go:225.26,226.61 1 0
eq2emu/internal/alt_advancement/master_list.go:226.61,228.5 1 0
eq2emu/internal/alt_advancement/master_list.go:232.3,232.49 1 0
eq2emu/internal/alt_advancement/master_list.go:232.49,234.4 1 0
eq2emu/internal/alt_advancement/master_list.go:236.3,236.49 1 0
eq2emu/internal/alt_advancement/master_list.go:236.49,238.4 1 0
eq2emu/internal/alt_advancement/master_list.go:241.3,241.65 1 0
eq2emu/internal/alt_advancement/master_list.go:241.65,243.4 1 0
eq2emu/internal/alt_advancement/master_list.go:245.3,245.61 1 0
eq2emu/internal/alt_advancement/master_list.go:245.61,247.4 1 0
eq2emu/internal/alt_advancement/master_list.go:250.2,250.15 1 0
eq2emu/internal/alt_advancement/master_list.go:254.60,265.43 8 0
eq2emu/internal/alt_advancement/master_list.go:265.43,267.3 1 0
eq2emu/internal/alt_advancement/master_list.go:268.2,270.14 2 0
eq2emu/internal/alt_advancement/master_list.go:274.46,282.2 1 1
eq2emu/internal/alt_advancement/master_list.go:285.69,286.17 1 1
eq2emu/internal/alt_advancement/master_list.go:286.17,288.3 1 1
eq2emu/internal/alt_advancement/master_list.go:290.2,294.56 3 1
eq2emu/internal/alt_advancement/master_list.go:294.56,296.3 1 1
eq2emu/internal/alt_advancement/master_list.go:299.2,305.44 3 1
eq2emu/internal/alt_advancement/master_list.go:305.44,307.3 1 1
eq2emu/internal/alt_advancement/master_list.go:308.2,312.12 3 1
eq2emu/internal/alt_advancement/master_list.go:316.62,322.37 4 1
eq2emu/internal/alt_advancement/master_list.go:322.37,325.3 2 1
eq2emu/internal/alt_advancement/master_list.go:327.2,327.15 1 1
eq2emu/internal/alt_advancement/master_list.go:331.82,335.60 3 1
eq2emu/internal/alt_advancement/master_list.go:335.60,338.33 2 1
eq2emu/internal/alt_advancement/master_list.go:338.33,341.4 2 1
eq2emu/internal/alt_advancement/master_list.go:342.3,342.16 1 1
eq2emu/internal/alt_advancement/master_list.go:345.2,345.26 1 1
eq2emu/internal/alt_advancement/master_list.go:349.71,353.54 3 1
eq2emu/internal/alt_advancement/master_list.go:353.54,356.3 2 1
eq2emu/internal/alt_advancement/master_list.go:358.2,358.12 1 1
eq2emu/internal/alt_advancement/master_list.go:362.42,367.2 3 1
eq2emu/internal/alt_advancement/master_list.go:370.50,378.2 6 1
eq2emu/internal/alt_advancement/master_list.go:381.51,386.2 3 0
eq2emu/internal/alt_advancement/master_list.go:389.52,394.41 4 0
eq2emu/internal/alt_advancement/master_list.go:394.41,396.3 1 0
eq2emu/internal/alt_advancement/master_list.go:398.2,398.42 1 0
eq2emu/internal/alt_advancement/master_list.go:398.42,400.3 1 0
eq2emu/internal/alt_advancement/master_list.go:402.2,402.16 1 0
eq2emu/internal/alt_advancement/master_list.go:406.59,414.37 5 0
eq2emu/internal/alt_advancement/master_list.go:414.37,416.3 1 0
eq2emu/internal/alt_advancement/master_list.go:419.2,420.37 2 0
eq2emu/internal/alt_advancement/master_list.go:420.37,422.24 2 0
eq2emu/internal/alt_advancement/master_list.go:422.24,424.4 1 0
eq2emu/internal/alt_advancement/master_list.go:425.3,425.27 1 0
eq2emu/internal/alt_advancement/master_list.go:428.2,428.15 1 0
eq2emu/internal/alt_advancement/master_list.go:432.65,443.51 8 0
eq2emu/internal/alt_advancement/master_list.go:443.51,445.3 1 0
eq2emu/internal/alt_advancement/master_list.go:446.2,448.14 2 0
eq2emu/internal/alt_advancement/master_list.go:452.76,457.29 3 0
eq2emu/internal/alt_advancement/master_list.go:457.29,458.13 1 0
eq2emu/internal/alt_advancement/master_list.go:458.13,460.4 1 0
eq2emu/internal/alt_advancement/master_list.go:463.2,463.16 1 0
eq2emu/internal/alt_advancement/master_list.go:467.78,470.27 2 0
eq2emu/internal/alt_advancement/master_list.go:470.27,472.3 1 0
eq2emu/internal/alt_advancement/master_list.go:474.2,474.10 1 0
eq2emu/internal/alt_advancement/types.go:283.47,299.2 1 1
eq2emu/internal/alt_advancement/types.go:302.57,316.2 1 1
eq2emu/internal/alt_advancement/types.go:319.62,331.2 1 1
eq2emu/internal/alt_advancement/types.go:334.54,347.2 1 0
eq2emu/internal/alt_advancement/types.go:350.51,353.2 2 1
eq2emu/internal/alt_advancement/types.go:356.43,362.2 1 1
eq2emu/internal/alt_advancement/types.go:365.39,366.15 1 1
eq2emu/internal/alt_advancement/types.go:367.16,368.22 1 1
eq2emu/internal/alt_advancement/types.go:369.19,370.25 1 1
eq2emu/internal/alt_advancement/types.go:371.17,372.24 1 1
eq2emu/internal/alt_advancement/types.go:373.17,374.23 1 1
eq2emu/internal/alt_advancement/types.go:375.21,376.27 1 1
eq2emu/internal/alt_advancement/types.go:377.19,378.25 1 1
eq2emu/internal/alt_advancement/types.go:379.30,380.36 1 1
eq2emu/internal/alt_advancement/types.go:381.17,382.23 1 1
eq2emu/internal/alt_advancement/types.go:383.22,384.28 1 1
eq2emu/internal/alt_advancement/types.go:385.18,386.24 1 1
eq2emu/internal/alt_advancement/types.go:387.10,388.13 1 1
eq2emu/internal/alt_advancement/types.go:393.36,394.47 1 1
eq2emu/internal/alt_advancement/types.go:394.47,396.3 1 1
eq2emu/internal/alt_advancement/types.go:397.2,397.18 1 1
eq2emu/internal/alt_advancement/types.go:401.46,402.57 1 1
eq2emu/internal/alt_advancement/types.go:402.57,404.3 1 1
eq2emu/internal/alt_advancement/types.go:405.2,405.18 1 1
eq2emu/internal/alt_advancement/types.go:409.59,411.2 1 1

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,570 @@
package alt_advancement
import (
"sync"
"sync/atomic"
"testing"
)
// TestAAManagerConcurrentPlayerAccess tests concurrent access to player states
func TestAAManagerConcurrentPlayerAccess(t *testing.T) {
config := DefaultAAManagerConfig()
manager := NewAAManager(config)
// Set up mock database
mockDB := &mockAADatabase{}
manager.SetDatabase(mockDB)
// Test concurrent access to the same player
const numGoroutines = 100
const characterID = int32(123)
var wg sync.WaitGroup
var successCount int64
// Launch multiple goroutines trying to get the same player state
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
state, err := manager.GetPlayerAAState(characterID)
if err != nil {
t.Errorf("Failed to get player state: %v", err)
return
}
if state == nil {
t.Error("Got nil player state")
return
}
if state.CharacterID != characterID {
t.Errorf("Wrong character ID: expected %d, got %d", characterID, state.CharacterID)
return
}
atomic.AddInt64(&successCount, 1)
}()
}
wg.Wait()
if atomic.LoadInt64(&successCount) != numGoroutines {
t.Errorf("Expected %d successful operations, got %d", numGoroutines, successCount)
}
// Verify only one instance was created in cache
manager.statesMutex.RLock()
cachedStates := len(manager.playerStates)
manager.statesMutex.RUnlock()
if cachedStates != 1 {
t.Errorf("Expected 1 cached state, got %d", cachedStates)
}
}
// TestAAManagerConcurrentMultiplePlayer tests concurrent access to different players
func TestAAManagerConcurrentMultiplePlayer(t *testing.T) {
config := DefaultAAManagerConfig()
manager := NewAAManager(config)
// Set up mock database
mockDB := &mockAADatabase{}
manager.SetDatabase(mockDB)
const numPlayers = 50
const goroutinesPerPlayer = 10
var wg sync.WaitGroup
var successCount int64
// Launch multiple goroutines for different players
for playerID := int32(1); playerID <= numPlayers; playerID++ {
for j := 0; j < goroutinesPerPlayer; j++ {
wg.Add(1)
go func(id int32) {
defer wg.Done()
state, err := manager.GetPlayerAAState(id)
if err != nil {
t.Errorf("Failed to get player state for %d: %v", id, err)
return
}
if state == nil {
t.Errorf("Got nil player state for %d", id)
return
}
if state.CharacterID != id {
t.Errorf("Wrong character ID: expected %d, got %d", id, state.CharacterID)
return
}
atomic.AddInt64(&successCount, 1)
}(playerID)
}
}
wg.Wait()
expectedSuccess := int64(numPlayers * goroutinesPerPlayer)
if atomic.LoadInt64(&successCount) != expectedSuccess {
t.Errorf("Expected %d successful operations, got %d", expectedSuccess, successCount)
}
// Verify correct number of cached states
manager.statesMutex.RLock()
cachedStates := len(manager.playerStates)
manager.statesMutex.RUnlock()
if cachedStates != numPlayers {
t.Errorf("Expected %d cached states, got %d", numPlayers, cachedStates)
}
}
// TestConcurrentAAPurchases tests concurrent AA purchases
func TestConcurrentAAPurchases(t *testing.T) {
config := DefaultAAManagerConfig()
manager := NewAAManager(config)
// Set up mock database
mockDB := &mockAADatabase{}
manager.SetDatabase(mockDB)
// Add test AAs
for i := 1; i <= 10; i++ {
aa := &AltAdvanceData{
SpellID: int32(i * 100),
NodeID: int32(i * 200),
Name: "Test AA",
Group: AA_CLASS,
MaxRank: 5,
RankCost: 1, // Low cost for testing
MinLevel: 1,
}
manager.masterAAList.AddAltAdvancement(aa)
}
// Get player state and give it points
state, err := manager.GetPlayerAAState(123)
if err != nil {
t.Fatalf("Failed to get player state: %v", err)
}
// Give player plenty of points
state.TotalPoints = 1000
state.AvailablePoints = 1000
const numGoroutines = 20
var wg sync.WaitGroup
var successCount, errorCount int64
// Concurrent purchases
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
// Try to purchase different AAs
aaNodeID := int32(200 + (goroutineID%10)*200) // Spread across different AAs
err := manager.PurchaseAA(123, aaNodeID, 1)
if err != nil {
atomic.AddInt64(&errorCount, 1)
// Some errors expected due to race conditions or insufficient points
} else {
atomic.AddInt64(&successCount, 1)
}
}(i)
}
wg.Wait()
t.Logf("Successful purchases: %d, Errors: %d", successCount, errorCount)
// Verify final state consistency
state.mutex.RLock()
finalAvailable := state.AvailablePoints
finalSpent := state.SpentPoints
finalTotal := state.TotalPoints
numProgress := len(state.AAProgress)
state.mutex.RUnlock()
// Basic consistency checks
if finalAvailable+finalSpent != finalTotal {
t.Errorf("Point consistency check failed: available(%d) + spent(%d) != total(%d)",
finalAvailable, finalSpent, finalTotal)
}
if numProgress > int(successCount) {
t.Errorf("More progress entries (%d) than successful purchases (%d)", numProgress, successCount)
}
t.Logf("Final state: Total=%d, Spent=%d, Available=%d, Progress entries=%d",
finalTotal, finalSpent, finalAvailable, numProgress)
}
// TestConcurrentAAPointAwarding tests concurrent point awarding
func TestConcurrentAAPointAwarding(t *testing.T) {
config := DefaultAAManagerConfig()
manager := NewAAManager(config)
// Set up mock database
mockDB := &mockAADatabase{}
manager.SetDatabase(mockDB)
const characterID = int32(123)
const numGoroutines = 100
const pointsPerAward = int32(10)
var wg sync.WaitGroup
var successCount int64
// Concurrent point awarding
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
err := manager.AwardAAPoints(characterID, pointsPerAward, "Concurrent test")
if err != nil {
t.Errorf("Failed to award points: %v", err)
return
}
atomic.AddInt64(&successCount, 1)
}(i)
}
wg.Wait()
if atomic.LoadInt64(&successCount) != numGoroutines {
t.Errorf("Expected %d successful awards, got %d", numGoroutines, successCount)
}
// Verify final point total
total, spent, available, err := manager.GetAAPoints(characterID)
if err != nil {
t.Fatalf("Failed to get AA points: %v", err)
}
expectedTotal := pointsPerAward * numGoroutines
if total != expectedTotal {
t.Errorf("Expected total points %d, got %d", expectedTotal, total)
}
if spent != 0 {
t.Errorf("Expected 0 spent points, got %d", spent)
}
if available != expectedTotal {
t.Errorf("Expected available points %d, got %d", expectedTotal, available)
}
}
// TestMasterAAListConcurrentOperations tests thread safety of MasterAAList
func TestMasterAAListConcurrentOperations(t *testing.T) {
masterList := NewMasterAAList()
// Pre-populate with some AAs
for i := 1; i <= 100; i++ {
aa := &AltAdvanceData{
SpellID: int32(i * 100),
NodeID: int32(i * 200),
Name: "Test AA",
Group: AA_CLASS,
MaxRank: 5,
RankCost: 2,
}
masterList.AddAltAdvancement(aa)
}
const numReaders = 50
const numWriters = 10
const operationsPerGoroutine = 100
var wg sync.WaitGroup
var readOps, writeOps int64
// Reader goroutines
for i := 0; i < numReaders; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
// Mix different read operations
switch j % 5 {
case 0:
masterList.GetAltAdvancement(100)
case 1:
masterList.GetAltAdvancementByNodeID(200)
case 2:
masterList.GetAAsByGroup(AA_CLASS)
case 3:
masterList.Size()
case 4:
masterList.GetAllAAs()
}
atomic.AddInt64(&readOps, 1)
}
}()
}
// Writer goroutines (adding new AAs)
for i := 0; i < numWriters; i++ {
wg.Add(1)
go func(writerID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
// Create unique AAs for each writer
baseID := (writerID + 1000) * 1000 + j
aa := &AltAdvanceData{
SpellID: int32(baseID),
NodeID: int32(baseID + 100000),
Name: "Concurrent AA",
Group: AA_CLASS,
MaxRank: 5,
RankCost: 2,
}
err := masterList.AddAltAdvancement(aa)
if err != nil {
// Some errors expected due to potential duplicates
continue
}
atomic.AddInt64(&writeOps, 1)
}
}(i)
}
wg.Wait()
t.Logf("Read operations: %d, Write operations: %d", readOps, writeOps)
// Verify final state
finalSize := masterList.Size()
if finalSize < 100 {
t.Errorf("Expected at least 100 AAs, got %d", finalSize)
}
t.Logf("Final AA count: %d", finalSize)
}
// TestMasterAANodeListConcurrentOperations tests thread safety of MasterAANodeList
func TestMasterAANodeListConcurrentOperations(t *testing.T) {
nodeList := NewMasterAANodeList()
// Pre-populate with some nodes
for i := 1; i <= 50; i++ {
node := &TreeNodeData{
ClassID: int32(i % 10 + 1), // Classes 1-10
TreeID: int32(i * 100),
AATreeID: int32(i * 200),
}
nodeList.AddTreeNode(node)
}
const numReaders = 30
const numWriters = 5
const operationsPerGoroutine = 100
var wg sync.WaitGroup
var readOps, writeOps int64
// Reader goroutines
for i := 0; i < numReaders; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
// Mix different read operations
switch j % 4 {
case 0:
nodeList.GetTreeNode(100)
case 1:
nodeList.GetTreeNodesByClass(1)
case 2:
nodeList.Size()
case 3:
nodeList.GetTreeNodes()
}
atomic.AddInt64(&readOps, 1)
}
}()
}
// Writer goroutines
for i := 0; i < numWriters; i++ {
wg.Add(1)
go func(writerID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
// Create unique nodes for each writer
baseID := (writerID + 1000) * 1000 + j
node := &TreeNodeData{
ClassID: int32(writerID%5 + 1),
TreeID: int32(baseID),
AATreeID: int32(baseID + 100000),
}
err := nodeList.AddTreeNode(node)
if err != nil {
// Some errors expected due to potential duplicates
continue
}
atomic.AddInt64(&writeOps, 1)
}
}(i)
}
wg.Wait()
t.Logf("Read operations: %d, Write operations: %d", readOps, writeOps)
// Verify final state
finalSize := nodeList.Size()
if finalSize < 50 {
t.Errorf("Expected at least 50 nodes, got %d", finalSize)
}
t.Logf("Final node count: %d", finalSize)
}
// TestAAPlayerStateConcurrentAccess tests thread safety of AAPlayerState
func TestAAPlayerStateConcurrentAccess(t *testing.T) {
playerState := NewAAPlayerState(123)
// Give player some initial points
playerState.TotalPoints = 1000
playerState.AvailablePoints = 1000
const numGoroutines = 100
var wg sync.WaitGroup
// Concurrent operations on player state
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
// Mix of different operations
switch goroutineID % 4 {
case 0:
// Add AA progress
progress := &PlayerAAData{
CharacterID: 123,
NodeID: int32(goroutineID + 1000),
CurrentRank: 1,
PointsSpent: 2,
}
playerState.AddAAProgress(progress)
case 1:
// Update points
playerState.UpdatePoints(1000, int32(goroutineID), 0)
case 2:
// Get AA progress
playerState.GetAAProgress(int32(goroutineID + 1000))
case 3:
// Calculate spent points
playerState.CalculateSpentPoints()
}
}(i)
}
wg.Wait()
// Verify state is still consistent
playerState.mutex.RLock()
totalPoints := playerState.TotalPoints
progressCount := len(playerState.AAProgress)
playerState.mutex.RUnlock()
if totalPoints != 1000 {
t.Errorf("Expected total points to remain 1000, got %d", totalPoints)
}
t.Logf("Final progress entries: %d", progressCount)
}
// TestConcurrentSystemOperations tests mixed system operations
func TestConcurrentSystemOperations(t *testing.T) {
config := DefaultAAManagerConfig()
manager := NewAAManager(config)
// Set up mock database
mockDB := &mockAADatabase{}
manager.SetDatabase(mockDB)
// Add some test AAs
for i := 1; i <= 20; i++ {
aa := &AltAdvanceData{
SpellID: int32(i * 100),
NodeID: int32(i * 200),
Name: "Test AA",
Group: int8(i % 3), // Mix groups
MaxRank: 5,
RankCost: 2,
MinLevel: 1,
}
manager.masterAAList.AddAltAdvancement(aa)
}
const numGoroutines = 50
var wg sync.WaitGroup
var operations int64
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
playerID := int32(goroutineID%10 + 1) // 10 different players
// Mix of operations
switch goroutineID % 6 {
case 0:
// Get player state
manager.GetPlayerAAState(playerID)
case 1:
// Award points
manager.AwardAAPoints(playerID, 50, "Test")
case 2:
// Get AA points
manager.GetAAPoints(playerID)
case 3:
// Get AAs by group
manager.GetAAsByGroup(AA_CLASS)
case 4:
// Get system stats
manager.GetSystemStats()
case 5:
// Try to purchase AA (might fail, that's ok)
manager.PurchaseAA(playerID, 200, 1)
}
atomic.AddInt64(&operations, 1)
}(i)
}
wg.Wait()
if atomic.LoadInt64(&operations) != numGoroutines {
t.Errorf("Expected %d operations, got %d", numGoroutines, operations)
}
t.Logf("Completed %d concurrent system operations", operations)
}

View File

@ -116,24 +116,22 @@ func (am *AAManager) ReloadAAData() error {
// LoadPlayerAA loads AA data for a specific player
func (am *AAManager) LoadPlayerAA(characterID int32) (*AAPlayerState, error) {
// For backwards compatibility, delegate to GetPlayerAAState
// Note: GetPlayerAAState already handles thread safety
return am.GetPlayerAAState(characterID)
}
// loadPlayerAAFromDatabase loads player data directly from database (internal helper)
func (am *AAManager) loadPlayerAAFromDatabase(characterID int32) (*AAPlayerState, error) {
if am.database == nil {
return nil, fmt.Errorf("database not configured")
}
// Load from database
playerState, err := am.database.LoadPlayerAA(characterID)
if err != nil {
return nil, fmt.Errorf("failed to load player AA: %v", err)
}
// Cache the player state
am.statesMutex.Lock()
am.playerStates[characterID] = playerState
am.statesMutex.Unlock()
// Fire load event
am.firePlayerAALoadedEvent(characterID, playerState)
return playerState, nil
}
@ -155,21 +153,44 @@ func (am *AAManager) SavePlayerAA(characterID int32) error {
// GetPlayerAAState returns the AA state for a player
func (am *AAManager) GetPlayerAAState(characterID int32) (*AAPlayerState, error) {
// Try to get from cache first
if playerState := am.getPlayerState(characterID); playerState != nil {
// Try to get from cache first (read lock)
am.statesMutex.RLock()
if playerState, exists := am.playerStates[characterID]; exists {
am.statesMutex.RUnlock()
return playerState, nil
}
am.statesMutex.RUnlock()
// Need to load from database, use write lock to prevent race condition
am.statesMutex.Lock()
defer am.statesMutex.Unlock()
// Double-check pattern: another goroutine might have loaded it while we waited
if playerState, exists := am.playerStates[characterID]; exists {
return playerState, nil
}
// Load from database if not cached
return am.LoadPlayerAA(characterID)
playerState, err := am.loadPlayerAAFromDatabase(characterID)
if err != nil {
return nil, err
}
// Cache the player state (already have write lock)
am.playerStates[characterID] = playerState
// Fire load event
am.firePlayerAALoadedEvent(characterID, playerState)
return playerState, nil
}
// PurchaseAA purchases an AA for a player
func (am *AAManager) PurchaseAA(characterID int32, nodeID int32, targetRank int8) error {
// Get player state
playerState := am.getPlayerState(characterID)
if playerState == nil {
return fmt.Errorf("player state not found")
// Get player state - this handles loading if needed
playerState, err := am.GetPlayerAAState(characterID)
if err != nil {
return fmt.Errorf("failed to get player state: %v", err)
}
// Get AA data
@ -187,7 +208,7 @@ func (am *AAManager) PurchaseAA(characterID int32, nodeID int32, targetRank int8
}
// Perform purchase
err := am.performAAPurchase(playerState, aaData, targetRank)
err = am.performAAPurchase(playerState, aaData, targetRank)
if err != nil {
return err
}
@ -355,16 +376,19 @@ func (am *AAManager) GetAATemplates(characterID int32) (map[int8]*AATemplate, er
// AwardAAPoints awards AA points to a player
func (am *AAManager) AwardAAPoints(characterID int32, points int32, reason string) error {
// Get player state
playerState := am.getPlayerState(characterID)
if playerState == nil {
return fmt.Errorf("player state not found")
// Get player state - this handles loading if needed
playerState, err := am.GetPlayerAAState(characterID)
if err != nil {
return fmt.Errorf("failed to get player state: %v", err)
}
// Award points
// Award points and capture values for events
var oldTotal, newTotal int32
playerState.mutex.Lock()
oldTotal = playerState.TotalPoints
playerState.TotalPoints += points
playerState.AvailablePoints += points
newTotal = playerState.TotalPoints
playerState.needsSync = true
playerState.mutex.Unlock()
@ -373,8 +397,8 @@ func (am *AAManager) AwardAAPoints(characterID int32, points int32, reason strin
am.notifier.NotifyAAPointsAwarded(characterID, points, reason)
}
// Fire points changed event
am.firePlayerAAPointsChangedEvent(characterID, playerState.TotalPoints-points, playerState.TotalPoints)
// Fire points changed event with captured values
am.firePlayerAAPointsChangedEvent(characterID, oldTotal, newTotal)
return nil
}
@ -520,15 +544,15 @@ func (am *AAManager) performAAPurchase(playerState *AAPlayerState, aaData *AltAd
// Calculate cost
pointsCost := int32(aaData.RankCost) * int32(targetRank)
// Check if player has enough points
// Update player state - MUST acquire lock BEFORE checking points to prevent TOCTOU race
playerState.mutex.Lock()
defer playerState.mutex.Unlock()
// Check if player has enough points (inside the lock to prevent race condition)
if playerState.AvailablePoints < pointsCost {
return fmt.Errorf("insufficient AA points: need %d, have %d", pointsCost, playerState.AvailablePoints)
}
// Update player state
playerState.mutex.Lock()
defer playerState.mutex.Unlock()
// Create or update progress
progress := playerState.AAProgress[aaData.NodeID]
if progress == nil {