Skip to content

Commit bf87012

Browse files
committed
impl: rework RecipeManager and RecipeIndex
* sort custom recipes based on priority * immutable RecipeIndex
1 parent d04419c commit bf87012

File tree

11 files changed

+174
-107
lines changed

11 files changed

+174
-107
lines changed

api/src/main/kotlin/com/wolfyscript/customcrafting/recipes/RecipeManager.kt

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.wolfyscript.customcrafting.recipes
22

33
import com.wolfyscript.customcrafting.recipes.data.RecipeEvaluationResult
44
import com.wolfyscript.customcrafting.recipes.data.RecipeInput
5+
import com.wolfyscript.customcrafting.resource.LoadedRecipe
56
import com.wolfyscript.scafall.identifier.Key
67

78
/**
@@ -15,6 +16,10 @@ import com.wolfyscript.scafall.identifier.Key
1516
*/
1617
interface RecipeManager {
1718

19+
companion object {
20+
const val LOG_PREFIX = "[Recipe Manager] "
21+
}
22+
1823
fun <I: RecipeInput, D: RecipeEvaluationResult.Data, T: CustomRecipe<I,D>> evaluateRecipesOfType(type: RecipeType<T>, input: I, context: EvaluationContext): RecipeEvaluationResult<D,T>?
1924

2025
fun disableRecipe(recipe: Key)
@@ -25,10 +30,38 @@ interface RecipeManager {
2530

2631
val disabledRecipes: Set<Key>
2732

28-
fun getRecipe(key: Key): CustomRecipe<*,*>?
33+
/**
34+
* Gets a [RecipeReference] to the recipe with the given [key].
35+
*
36+
* While the reference can be cached, the recipe value of the reference should not be cached separately!
37+
*/
38+
fun getRecipe(key: Key): RecipeReference<*>?
39+
40+
/**
41+
* Registers new recipes or updates existing recipes in the [RecipeManager] and re-indexes them by type and key.
42+
*/
43+
fun registerOrUpdateRecipes(recipes: Collection<LoadedRecipe>)
44+
45+
/**
46+
* Removes the recipes from the [RecipeManager] and re-indexes the recipes by type and key.
47+
*/
48+
fun removeRecipes(vararg recipes: Key)
2949

30-
fun removeRecipe(key: Key)
50+
/**
51+
* Gets all the recipe references in the [RecipeManager].
52+
*/
53+
fun recipes(): Collection<RecipeReference<*>>
3154

32-
fun updateRecipe(key: Key, recipe: CustomRecipe<*,*>)
55+
}
3356

57+
inline fun <reified T : CustomRecipe<*, *>> RecipeManager.getRecipeTyped(key: Key, type: RecipeType<T>): RecipeReference<T>? {
58+
val ref = getRecipe(key) ?: return null
59+
if (ref.type != type) {
60+
return null
61+
}
62+
return try {
63+
ref as? RecipeReference<T>
64+
} catch (_: ClassCastException) {
65+
null
66+
}
3467
}

api/src/main/kotlin/com/wolfyscript/customcrafting/recipes/RecipeReference.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import com.wolfyscript.scafall.identifier.Key
1313
*
1414
* It should always be checked if the recipe is available before accessing it (even if you registered a recipe yourself, other plugins may remove it).
1515
*/
16-
interface RecipeReference<T: CustomRecipe<*,*>> {
16+
interface RecipeReference<out T: CustomRecipe<*,*>> {
1717

1818
/**
1919
* The key of the recipe in the RecipeIndex

common/src/main/kotlin/com/wolfyscript/customcrafting/core/commands/RecipesCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ object RecipesCommand {
7878
private fun printStatus(ctx: CommandContext<CommandSourceStack>, customCrafting: CustomCraftingCommon) {
7979
val recipeManager = customCrafting.recipeManager
8080

81-
val totalRecipeCount = recipeManager.index.values().count()
81+
val totalRecipeCount = recipeManager.recipes().count()
8282
val ccRecipesCount = recipeManager.recipesLoadedByCC.size
8383
val thirdPartyRecipeCount = totalRecipeCount - ccRecipesCount
8484
val disabledRecipeCount = recipeManager.disabledRecipes.size

common/src/main/kotlin/com/wolfyscript/customcrafting/recipes/RecipeIndex.kt

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,107 @@
11
package com.wolfyscript.customcrafting.recipes
22

3-
import com.google.common.collect.Multimap
4-
import com.google.common.collect.Multimaps
3+
import com.google.common.collect.ImmutableList
4+
import com.google.common.collect.ImmutableListMultimap
5+
import com.google.common.collect.ImmutableMap
56
import com.wolfyscript.customcrafting.recipes.data.RecipeEvaluationResult
67
import com.wolfyscript.customcrafting.recipes.data.RecipeInput
8+
import com.wolfyscript.customcrafting.resource.LoadedRecipe
79
import com.wolfyscript.scafall.identifier.Key
8-
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
9-
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
1010
import java.util.*
1111

12-
class RecipeIndex {
12+
/**
13+
* The internal index of all the custom recipes.
14+
* Recipes are indexed by their [Key] and [RecipeType].
15+
*
16+
* This index is immutable, and should be updated using [registerOrUpdateAll] and [removeBatch].
17+
* That way, the index is thread-safe and prevents concurrent modification issues.
18+
*
19+
* [RecipeReferences][RecipeReference] are used to prevent caching of the recipe objects directly and causing memory leaks, because the objects may be removed/updated at any time.
20+
*/
21+
internal class RecipeIndex {
1322

14-
private val recipes: MutableSet<CustomRecipe<*, *>> = ObjectOpenHashSet()
23+
companion object {
24+
private val recipeValueComparator: Comparator<RecipeReference<*>> = Comparator.comparing { it.value?.priority ?: 0 }
25+
}
1526

27+
private val recipes: List<CustomRecipe<*, *>>
1628
// reference those recipes, but do not hold on to those object references directly
17-
internal val byKey: MutableMap<Key, RecipeReference<*>> = Object2ObjectOpenHashMap()
18-
internal val byType: Multimap<RecipeType<*>, RecipeReference<*>> =
19-
Multimaps.newSetMultimap(Object2ObjectOpenHashMap()) { ObjectOpenHashSet() }
29+
internal val byKey: Map<Key, RecipeReference<*>>
30+
internal val byType: ImmutableListMultimap<RecipeType<*>, RecipeReference<*>>
2031

21-
fun values(): Collection<RecipeReference<*>> {
22-
return Collections.unmodifiableCollection(byKey.values)
23-
}
32+
constructor(recipes: Collection<LoadedRecipe>) {
33+
val recipesBuilder = ImmutableList.builder<CustomRecipe<*, *>>()
34+
val byKeyBuilder = ImmutableMap.builder<Key, RecipeReference<*>>()
35+
val byTypeBuilder = ImmutableListMultimap.Builder<RecipeType<*>, RecipeReference<*>>()
36+
byTypeBuilder.orderValuesBy(recipeValueComparator)
2437

25-
fun <T : CustomRecipe<*, *>> register(key: Key, recipe: T): RecipeReference<T> = synchronized(this) {
26-
if (byKey.containsKey(key)) {
27-
return byKey[key]!! as RecipeReference<T>
38+
recipes.forEach {
39+
recipesBuilder.add(it.recipe)
40+
val ref = RecipeReferenceImpl(it.key, it.recipe)
41+
byTypeBuilder.put(it.recipe.type, ref)
42+
byKeyBuilder.put(it.key, ref)
2843
}
29-
val ref = RecipeReferenceImpl(key, recipe)
30-
byKey[key] = ref
31-
byType.put(recipe.type, ref)
32-
return ref
44+
45+
this.byType = byTypeBuilder.build()
46+
this.byKey = byKeyBuilder.build()
47+
this.recipes = recipesBuilder.build()
48+
}
49+
50+
constructor(recipes: List<CustomRecipe<*, *>>, byKey: Map<Key, RecipeReference<*>>, byType: ImmutableListMultimap<RecipeType<*>, RecipeReference<*>>) {
51+
this.recipes = recipes
52+
this.byKey = byKey
53+
this.byType = byType
54+
}
55+
56+
fun values(): Collection<RecipeReference<*>> {
57+
return Collections.unmodifiableCollection(byKey.values)
3358
}
3459

3560
fun get(key: Key): RecipeReference<*>? = synchronized(this) {
3661
return byKey[key]
3762
}
3863

39-
fun remove(key: Key) = synchronized(this) {
40-
val recipeRef = byKey[key]
41-
if (recipeRef != null) {
42-
val recipe = recipeRef.value
43-
recipes.remove(recipe)
44-
byType.remove(recipeRef.type, recipeRef)
45-
byKey.remove(key)
64+
fun registerOrUpdateAll(recipes: Collection<LoadedRecipe>) : RecipeIndex {
65+
val updatedRecipes = ArrayList<CustomRecipe<*,*>>(recipes.size + this.recipes.size)
66+
val byKeyBuilder = ImmutableMap.builder<Key, RecipeReference<*>>()
67+
val byTypeBuilder = ImmutableListMultimap.Builder<RecipeType<*>, RecipeReference<*>>()
68+
byTypeBuilder.orderValuesBy(recipeValueComparator)
69+
70+
updatedRecipes.addAll(this.recipes)
71+
val updatedByKey = byKey.toMutableMap()
72+
for (recipe in recipes) {
73+
val existing = updatedByKey.remove(recipe.key)
74+
updatedRecipes.remove(existing?.value)
75+
updatedRecipes.add(recipe.recipe)
76+
77+
val ref = RecipeReferenceImpl(recipe.key, recipe.recipe)
78+
byTypeBuilder.put(recipe.recipe.type, ref)
79+
byKeyBuilder.put(recipe.key, ref)
4680
}
81+
82+
return RecipeIndex(updatedRecipes, byKeyBuilder.build(), byTypeBuilder.build())
4783
}
4884

49-
inline fun <reified T : CustomRecipe<*, *>> getRecipeTyped(key: Key, type: RecipeType<T>): T? = synchronized(this) {
50-
val recipe = get(key) ?: return null
51-
if (recipe.type != type) {
52-
return null
85+
fun removeBatch(vararg keys: Key) : RecipeIndex {
86+
val updatedByKey = byKey.toMutableMap()
87+
for (key in keys) {
88+
updatedByKey.remove(key)
89+
}
90+
val recipesBuilder = ImmutableList.builder<CustomRecipe<*, *>>()
91+
val byTypeBuilder = ImmutableListMultimap.Builder<RecipeType<*>, RecipeReference<*>>()
92+
byTypeBuilder.orderValuesBy(recipeValueComparator)
93+
updatedByKey.keys.forEach { key ->
94+
val ref = byKey[key]
95+
ref?.value?.let { recipe ->
96+
recipesBuilder.add(recipe)
97+
val ref = RecipeReferenceImpl(key, recipe)
98+
byTypeBuilder.put(recipe.type, ref)
99+
}
53100
}
54-
return type.recipeClass.cast(recipe)
101+
return RecipeIndex(recipesBuilder.build(), updatedByKey.toMap(), byTypeBuilder.build())
55102
}
56103

57-
fun <I : RecipeInput, D : RecipeEvaluationResult.Data, T : CustomRecipe<I, D>> byType(type: RecipeType<T>): Collection<RecipeReference<T>> = synchronized(this) {
104+
fun <I : RecipeInput, D : RecipeEvaluationResult.Data, T : CustomRecipe<I, D>> byType(type: RecipeType<T>): Collection<RecipeReference<T>> {
58105
return byType.get(type) as Collection<RecipeReference<T>>
59106
}
60107

0 commit comments

Comments
 (0)