|
1 | 1 | package com.wolfyscript.customcrafting.recipes |
2 | 2 |
|
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 |
5 | 6 | import com.wolfyscript.customcrafting.recipes.data.RecipeEvaluationResult |
6 | 7 | import com.wolfyscript.customcrafting.recipes.data.RecipeInput |
| 8 | +import com.wolfyscript.customcrafting.resource.LoadedRecipe |
7 | 9 | import com.wolfyscript.scafall.identifier.Key |
8 | | -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap |
9 | | -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet |
10 | 10 | import java.util.* |
11 | 11 |
|
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 { |
13 | 22 |
|
14 | | - private val recipes: MutableSet<CustomRecipe<*, *>> = ObjectOpenHashSet() |
| 23 | + companion object { |
| 24 | + private val recipeValueComparator: Comparator<RecipeReference<*>> = Comparator.comparing { it.value?.priority ?: 0 } |
| 25 | + } |
15 | 26 |
|
| 27 | + private val recipes: List<CustomRecipe<*, *>> |
16 | 28 | // 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<*>> |
20 | 31 |
|
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) |
24 | 37 |
|
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) |
28 | 43 | } |
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) |
33 | 58 | } |
34 | 59 |
|
35 | 60 | fun get(key: Key): RecipeReference<*>? = synchronized(this) { |
36 | 61 | return byKey[key] |
37 | 62 | } |
38 | 63 |
|
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) |
46 | 80 | } |
| 81 | + |
| 82 | + return RecipeIndex(updatedRecipes, byKeyBuilder.build(), byTypeBuilder.build()) |
47 | 83 | } |
48 | 84 |
|
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 | + } |
53 | 100 | } |
54 | | - return type.recipeClass.cast(recipe) |
| 101 | + return RecipeIndex(recipesBuilder.build(), updatedByKey.toMap(), byTypeBuilder.build()) |
55 | 102 | } |
56 | 103 |
|
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>> { |
58 | 105 | return byType.get(type) as Collection<RecipeReference<T>> |
59 | 106 | } |
60 | 107 |
|
|
0 commit comments