mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-03 13:58:28 +08:00
Improvements to JS-specific code and documentation and added a unit test.
This commit is contained in:
parent
f4ad5b93f9
commit
9653a982c0
@ -3,12 +3,15 @@ package world.phantasmal.core.disposable
|
||||
/**
|
||||
* A global count is kept of all undisposed instances of this class.
|
||||
* This count can be used to find memory leaks.
|
||||
*
|
||||
* Tracking is not thread-safe.
|
||||
*/
|
||||
abstract class TrackedDisposable : Disposable {
|
||||
var disposed = false
|
||||
private set
|
||||
|
||||
init {
|
||||
// Suppress this warning, because track simply adds this disposable to a set at this point.
|
||||
@Suppress("LeakingThis")
|
||||
track(this)
|
||||
}
|
||||
|
@ -2,8 +2,22 @@ package world.phantasmal.core.unsafe
|
||||
|
||||
/**
|
||||
* Map optimized for JS (it compiles to the built-in Map).
|
||||
*
|
||||
* In JS, keys are compared by reference, equals and hashCode are NOT invoked. On JVM, equals and
|
||||
* hashCode ARE used.
|
||||
*
|
||||
* DO NOT USE THIS UNLESS ALL THE FOLLOWING REQUIREMENTS ARE MET:
|
||||
*
|
||||
* 1. It improves performance substantially.
|
||||
* If it doesn't improve performance by a very noticeable amount, it's not worth the risk.
|
||||
*
|
||||
* 2. It's only used internally.
|
||||
* E.g. in a private property which no other code can access and misuse accidentally. This way
|
||||
* only a small part of the code can contain hard to discover errors.
|
||||
*
|
||||
* 3. The keys used do not require equals or hashCode to be called in JS.
|
||||
* E.g. Int, String, objects which you consider equal if and only if they are the exact same
|
||||
* instance.
|
||||
*/
|
||||
expect class UnsafeMap<K, V>() {
|
||||
fun get(key: K): V?
|
||||
@ -13,7 +27,7 @@ expect class UnsafeMap<K, V>() {
|
||||
fun delete(key: K): Boolean
|
||||
}
|
||||
|
||||
fun <K, V : Any> UnsafeMap<K, V>.getOrPut(key: K, default: () -> V): V {
|
||||
inline fun <K, V : Any> UnsafeMap<K, V>.getOrPut(key: K, default: () -> V): V {
|
||||
var value = get(key)
|
||||
|
||||
if (value == null) {
|
||||
|
@ -0,0 +1,38 @@
|
||||
package world.phantasmal.core.unsafe
|
||||
|
||||
/**
|
||||
* Set optimized for JS (it compiles to the built-in Set).
|
||||
*
|
||||
* In JS, values are compared by reference, equals and hashCode are NOT invoked. On JVM, equals and
|
||||
* hashCode ARE used.
|
||||
*
|
||||
* DO NOT USE THIS UNLESS ALL THE FOLLOWING REQUIREMENTS ARE MET:
|
||||
*
|
||||
* 1. It improves performance substantially.
|
||||
* If it doesn't improve performance by a very noticeable amount, it's not worth the risk.
|
||||
*
|
||||
* 2. It's only used internally.
|
||||
* E.g. in a private property which no other code can access and misuse accidentally. This way
|
||||
* only a small part of the code can contain hard to discover errors.
|
||||
*
|
||||
* 3. The values used do not require equals or hashCode to be called in JS.
|
||||
* E.g. Int, String, objects which you consider equal if and only if they are the exact same
|
||||
* instance.
|
||||
*/
|
||||
expect class UnsafeSet<T> {
|
||||
constructor()
|
||||
constructor(values: Array<out T>)
|
||||
|
||||
val size: Int
|
||||
|
||||
fun add(value: T): UnsafeSet<T>
|
||||
fun clear()
|
||||
fun delete(value: T): Boolean
|
||||
fun has(value: T): Boolean
|
||||
fun forEach(callback: (value: T) -> Unit)
|
||||
}
|
||||
|
||||
/**
|
||||
* See the disclaimer at [UnsafeSet].
|
||||
*/
|
||||
fun <T> unsafeSetOf(vararg values: T): UnsafeSet<T> = UnsafeSet(values)
|
@ -2,6 +2,9 @@
|
||||
|
||||
package world.phantasmal.core
|
||||
|
||||
import org.w3c.dom.DOMRectReadOnly
|
||||
import org.w3c.dom.Element
|
||||
|
||||
external interface JsArray<T> {
|
||||
val length: Int
|
||||
|
||||
@ -43,23 +46,19 @@ inline val <T> JsPair<*, T>.second: T get() = asDynamic()[1].unsafeCast<T>()
|
||||
inline operator fun <T> JsPair<T, *>.component1(): T = first
|
||||
inline operator fun <T> JsPair<*, T>.component2(): T = second
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
inline fun objectEntries(jsObject: dynamic): Array<JsPair<String, dynamic>> =
|
||||
js("Object.entries(jsObject)").unsafeCast<Array<JsPair<String, dynamic>>>()
|
||||
|
||||
external interface JsSet<T> {
|
||||
val size: Int
|
||||
|
||||
fun add(value: T): JsSet<T>
|
||||
fun clear()
|
||||
fun delete(value: T): Boolean
|
||||
fun has(value: T): Boolean
|
||||
fun forEach(callback: (value: T) -> Unit)
|
||||
@JsName("Object")
|
||||
external class JsObject {
|
||||
companion object {
|
||||
fun entries(jsObject: dynamic): Array<JsPair<String, dynamic>>
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> emptyJsSet(): JsSet<T> =
|
||||
js("new Set()").unsafeCast<JsSet<T>>()
|
||||
external class ResizeObserver(callback: (entries: Array<ResizeObserverEntry>) -> Unit) {
|
||||
fun observe(target: Element)
|
||||
fun unobserve(target: Element)
|
||||
fun disconnect()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
inline fun <T> jsSetOf(vararg values: T): JsSet<T> =
|
||||
js("new Set(values)").unsafeCast<JsSet<T>>()
|
||||
external interface ResizeObserverEntry {
|
||||
val contentRect: DOMRectReadOnly
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package world.phantasmal.core.unsafe
|
||||
|
||||
@JsName("Set")
|
||||
actual external class UnsafeSet<T> {
|
||||
actual constructor()
|
||||
actual constructor(values: Array<out T>)
|
||||
|
||||
actual val size: Int
|
||||
|
||||
actual fun add(value: T): UnsafeSet<T>
|
||||
actual fun clear()
|
||||
actual fun delete(value: T): Boolean
|
||||
actual fun has(value: T): Boolean
|
||||
actual fun forEach(callback: (value: T) -> Unit)
|
||||
}
|
@ -1,14 +1,16 @@
|
||||
package world.phantasmal.core.unsafe
|
||||
|
||||
actual class UnsafeMap<K, V> {
|
||||
private val map = HashMap<K, V>()
|
||||
private val map = LinkedHashMap<K, V>()
|
||||
|
||||
actual fun get(key: K): V? = map[key]
|
||||
|
||||
actual fun has(key: K): Boolean = key in map
|
||||
|
||||
actual fun forEach(callback: (value: V, key: K) -> Unit) {
|
||||
map.forEach { (k, v) -> callback(v, k) }
|
||||
for ((k, v) in map) {
|
||||
callback(v, k)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun set(key: K, value: V) {
|
||||
|
@ -0,0 +1,27 @@
|
||||
package world.phantasmal.core.unsafe
|
||||
|
||||
actual class UnsafeSet<T>(private val set: LinkedHashSet<T>) {
|
||||
actual constructor() : this(LinkedHashSet())
|
||||
actual constructor(values: Array<out T>) : this(LinkedHashSet<T>().apply { addAll(values) })
|
||||
|
||||
actual val size: Int get() = set.size
|
||||
|
||||
actual fun add(value: T): UnsafeSet<T> {
|
||||
set.add(value)
|
||||
return this
|
||||
}
|
||||
|
||||
actual fun clear() {
|
||||
set.clear()
|
||||
}
|
||||
|
||||
actual fun delete(value: T): Boolean = set.remove(value)
|
||||
|
||||
actual fun has(value: T): Boolean = value in set
|
||||
|
||||
actual fun forEach(callback: (value: T) -> Unit) {
|
||||
for (v in set) {
|
||||
callback(v)
|
||||
}
|
||||
}
|
||||
}
|
@ -107,4 +107,27 @@ class AsmTokenizationTests : LibTestSuite {
|
||||
assertEquals(len, this.len)
|
||||
assertFalse(nextToken())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valid_identifiers_are_parsed_as_Ident_tokens() {
|
||||
val tokenizer = LineTokenizer()
|
||||
|
||||
tokenizer.testIdent(" opcode_mnemonic ", "opcode_mnemonic", col = 3, len = 15)
|
||||
tokenizer.testIdent("inst_1", "inst_1", col = 1, len = 6)
|
||||
}
|
||||
|
||||
private fun LineTokenizer.testIdent(
|
||||
line: String,
|
||||
value: String,
|
||||
col: Int,
|
||||
len: Int,
|
||||
) {
|
||||
tokenize(line)
|
||||
assertTrue(nextToken())
|
||||
assertEquals(Token.Ident, this.type)
|
||||
assertEquals(value, this.strValue)
|
||||
assertEquals(col, this.col)
|
||||
assertEquals(len, this.len)
|
||||
assertFalse(nextToken())
|
||||
}
|
||||
}
|
||||
|
@ -14,21 +14,8 @@ import world.phantasmal.web.core.dot
|
||||
import world.phantasmal.web.core.toQuaternion
|
||||
import world.phantasmal.web.externals.three.*
|
||||
import world.phantasmal.webui.obj
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.MutableList
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.forEachIndexed
|
||||
import kotlin.collections.getOrPut
|
||||
import kotlin.collections.indices
|
||||
import kotlin.collections.iterator
|
||||
import kotlin.collections.last
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.mutableListOf
|
||||
import kotlin.collections.mutableMapOf
|
||||
import kotlin.collections.sum
|
||||
import kotlin.collections.withIndex
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@ -92,10 +79,7 @@ private val tmpNormal = Vector3()
|
||||
private val tmpVec = Vector3()
|
||||
private val tmpNormalMatrix = Matrix3()
|
||||
|
||||
interface AreaObjectUserData {
|
||||
var sectionId: Int
|
||||
var areaObject: AreaObject
|
||||
}
|
||||
class AreaObjectUserData(val sectionId: Int, val areaObject: AreaObject)
|
||||
|
||||
fun ninjaObjectToMesh(
|
||||
ninjaObject: NinjaObject<*, *>,
|
||||
@ -141,6 +125,7 @@ fun ninjaObjectToMeshBuilder(
|
||||
NinjaToMeshConverter(builder).convert(ninjaObject)
|
||||
}
|
||||
|
||||
/** The returned group is not copyable because it contains non-serializable user data. */
|
||||
fun renderGeometryToGroup(
|
||||
renderGeometry: AreaGeometry,
|
||||
textures: List<XvrTexture?>,
|
||||
@ -221,6 +206,7 @@ fun AreaObject.fingerPrint(): String =
|
||||
append(radius.toRawBits().toUInt().toString(36))
|
||||
}
|
||||
|
||||
/** The returned mesh is not copyable because it contains non-serializable user data. */
|
||||
private fun areaObjectToMesh(
|
||||
textures: List<XvrTexture?>,
|
||||
textureCache: UnsafeMap<Int, Texture?>,
|
||||
@ -230,9 +216,10 @@ private fun areaObjectToMesh(
|
||||
areaObj: AreaObject,
|
||||
processMesh: (AreaSection, AreaObject, Mesh) -> Unit,
|
||||
): Mesh {
|
||||
var mesh = meshCache.get(areaObj.xjObject)
|
||||
val cachedMesh = meshCache.get(areaObj.xjObject)
|
||||
val mesh: Mesh
|
||||
|
||||
if (mesh == null) {
|
||||
if (cachedMesh == null) {
|
||||
val builder = MeshBuilder(textures, textureCache)
|
||||
ninjaObjectToMeshBuilder(areaObj.xjObject, builder)
|
||||
|
||||
@ -248,13 +235,10 @@ private fun areaObjectToMesh(
|
||||
} else {
|
||||
// If we already have a mesh for this XjObject, make a copy and reuse the existing buffer
|
||||
// geometry and materials.
|
||||
mesh = Mesh(mesh.geometry, mesh.material.unsafeCast<Array<Material>>())
|
||||
mesh = Mesh(cachedMesh.geometry, cachedMesh.material.unsafeCast<Array<Material>>())
|
||||
}
|
||||
|
||||
val userData = mesh.userData.unsafeCast<AreaObjectUserData>()
|
||||
userData.sectionId = section.id
|
||||
userData.areaObject = areaObj
|
||||
|
||||
mesh.userData = AreaObjectUserData(section.id, areaObj)
|
||||
mesh.position.setFromVec3(section.position)
|
||||
mesh.rotation.setFromVec3(section.rotation)
|
||||
mesh.updateMatrixWorld()
|
||||
|
@ -4,13 +4,16 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mu.KotlinLogging
|
||||
import world.phantasmal.core.*
|
||||
import world.phantasmal.core.JsObject
|
||||
import world.phantasmal.core.component1
|
||||
import world.phantasmal.core.component2
|
||||
import world.phantasmal.core.unsafe.UnsafeMap
|
||||
import world.phantasmal.psolib.fileFormats.quest.NpcType
|
||||
import world.phantasmal.core.unsafe.UnsafeSet
|
||||
import world.phantasmal.observable.cell.Cell
|
||||
import world.phantasmal.observable.cell.list.ListCell
|
||||
import world.phantasmal.observable.cell.list.mutableListCell
|
||||
import world.phantasmal.observable.cell.mutableCell
|
||||
import world.phantasmal.psolib.fileFormats.quest.NpcType
|
||||
import world.phantasmal.web.core.models.Server
|
||||
import world.phantasmal.web.core.stores.EnemyDropTable
|
||||
import world.phantasmal.web.core.stores.ItemDropStore
|
||||
@ -138,7 +141,7 @@ class HuntOptimizerStore(
|
||||
// Each variable has a matching FullMethod.
|
||||
val fullMethods = UnsafeMap<String, FullMethod>()
|
||||
|
||||
val wantedItemTypeIds = emptyJsSet<Int>()
|
||||
val wantedItemTypeIds = UnsafeSet<Int>()
|
||||
|
||||
for (wanted in filteredWantedItems) {
|
||||
wantedItemTypeIds.add(wanted.itemType.id)
|
||||
@ -191,7 +194,7 @@ class HuntOptimizerStore(
|
||||
|
||||
private fun createFullMethods(
|
||||
dropTable: EnemyDropTable,
|
||||
wantedItemTypeIds: JsSet<Int>,
|
||||
wantedItemTypeIds: UnsafeSet<Int>,
|
||||
method: HuntMethodModel,
|
||||
defaultCounts: UnsafeMap<NpcType, Double>,
|
||||
splitPanArms: Boolean,
|
||||
@ -260,7 +263,7 @@ class HuntOptimizerStore(
|
||||
}
|
||||
|
||||
private fun solve(
|
||||
wantedItemTypeIds: JsSet<Int>,
|
||||
wantedItemTypeIds: UnsafeSet<Int>,
|
||||
constraints: dynamic,
|
||||
variables: dynamic,
|
||||
fullMethods: UnsafeMap<String, FullMethod>,
|
||||
@ -277,13 +280,13 @@ class HuntOptimizerStore(
|
||||
}
|
||||
|
||||
// Loop over the entries in result, ignore standard properties that aren't variables.
|
||||
return objectEntries(result).mapNotNull { (variableName, runsOrOther) ->
|
||||
return JsObject.entries(result).mapNotNull { (variableName, runsOrOther) ->
|
||||
fullMethods.get(variableName)?.let { fullMethod ->
|
||||
val runs = runsOrOther as Double
|
||||
val variable = variables[variableName]
|
||||
|
||||
val itemTypeIdToCount: Map<Int, Double> =
|
||||
objectEntries(variable)
|
||||
JsObject.entries(variable)
|
||||
.mapNotNull { (itemTypeIdStr, expectedAmount) ->
|
||||
itemTypeIdStr.toIntOrNull()?.let { itemTypeId ->
|
||||
if (wantedItemTypeIds.has(itemTypeId)) {
|
||||
@ -318,7 +321,7 @@ class HuntOptimizerStore(
|
||||
if (v == null) {
|
||||
matchFound = false
|
||||
} else {
|
||||
for ((itemName, expectedAmount) in objectEntries(variable)) {
|
||||
for ((itemName, expectedAmount) in JsObject.entries(variable)) {
|
||||
if (v[itemName] != expectedAmount.unsafeCast<Double>()) {
|
||||
matchFound = false
|
||||
break
|
||||
|
@ -2,6 +2,8 @@ package world.phantasmal.web.questEditor.loading
|
||||
|
||||
import org.khronos.webgl.ArrayBuffer
|
||||
import world.phantasmal.core.*
|
||||
import world.phantasmal.core.unsafe.UnsafeSet
|
||||
import world.phantasmal.core.unsafe.unsafeSetOf
|
||||
import world.phantasmal.psolib.Endianness
|
||||
import world.phantasmal.psolib.Episode
|
||||
import world.phantasmal.psolib.cursor.cursor
|
||||
@ -20,9 +22,7 @@ import world.phantasmal.webui.DisposableContainer
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
|
||||
interface AreaUserData {
|
||||
var section: SectionModel?
|
||||
}
|
||||
class AreaUserData(val section: SectionModel?)
|
||||
|
||||
/**
|
||||
* Loads and caches area assets.
|
||||
@ -65,9 +65,11 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
suspend fun loadSections(episode: Episode, areaVariant: AreaVariantModel): List<SectionModel> =
|
||||
cache.get(EpisodeAndAreaVariant(episode, areaVariant)).sections
|
||||
|
||||
/** The returned object is not copyable because it contains non-serializable user data. */
|
||||
suspend fun loadRenderGeometry(episode: Episode, areaVariant: AreaVariantModel): Object3D =
|
||||
cache.get(EpisodeAndAreaVariant(episode, areaVariant)).renderGeometry
|
||||
|
||||
/** The returned object is not copyable because it contains non-serializable user data. */
|
||||
suspend fun loadCollisionGeometry(
|
||||
episode: Episode,
|
||||
areaVariant: AreaVariantModel,
|
||||
@ -94,14 +96,14 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
tmpIntersections.asJsArray().splice(0)
|
||||
val intersection1 = raycaster
|
||||
.intersectObject(renderGeom, true, tmpIntersections)
|
||||
.find { (it.`object`.userData.unsafeCast<AreaUserData>()).section != null }
|
||||
.find { it.`object`.userData is AreaUserData }
|
||||
|
||||
// Cast a ray upward from the center of the section.
|
||||
raycaster.set(origin, UP)
|
||||
tmpIntersections.asJsArray().splice(0)
|
||||
val intersection2 = raycaster
|
||||
.intersectObject(renderGeom, true, tmpIntersections)
|
||||
.find { (it.`object`.userData.unsafeCast<AreaUserData>()).section != null }
|
||||
.find { it.`object`.userData is AreaUserData }
|
||||
|
||||
// Choose the nearest intersection if we have 2.
|
||||
val intersection =
|
||||
@ -113,9 +115,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
}
|
||||
|
||||
if (intersection != null) {
|
||||
val cud = collisionMesh.userData.unsafeCast<AreaUserData>()
|
||||
val rud = intersection.`object`.userData.unsafeCast<AreaUserData>()
|
||||
cud.section = rud.section
|
||||
collisionMesh.userData = intersection.`object`.userData
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,7 +194,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
)
|
||||
}
|
||||
|
||||
(mesh.userData.unsafeCast<AreaUserData>()).section = sectionModel
|
||||
mesh.userData = AreaUserData(sectionModel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,12 +241,12 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
* regular geometry. Might not be necessary anymore once order-independent rendering is
|
||||
* implemented.
|
||||
*/
|
||||
private val renderOnTopTextures: JsSet<Int> = emptyJsSet(),
|
||||
private val renderOnTopTextures: UnsafeSet<Int> = UnsafeSet(),
|
||||
/**
|
||||
* Set of [AreaObject] finger prints.
|
||||
* These objects should be hidden because they cover floors and other useful geometry.
|
||||
*/
|
||||
private val hiddenObjects: JsSet<String> = emptyJsSet(),
|
||||
private val hiddenObjects: UnsafeSet<String> = UnsafeSet(),
|
||||
) {
|
||||
fun shouldRenderOnTop(obj: XjObject): Boolean {
|
||||
obj.model?.meshes?.let { meshes ->
|
||||
@ -330,7 +330,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
private val MANUAL_FIXES: Map<Pair<Episode, Int>, Fix> = mutableMapOf(
|
||||
// Pioneer 2
|
||||
Pair(Episode.I, 0) to Fix(
|
||||
renderOnTopTextures = jsSetOf(
|
||||
renderOnTopTextures = unsafeSetOf(
|
||||
20,
|
||||
40,
|
||||
41,
|
||||
@ -360,7 +360,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
234,
|
||||
243,
|
||||
),
|
||||
hiddenObjects = jsSetOf(
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_m_0_6a_d_iu5sg6",
|
||||
"s_m_0_4b_7_ioh738",
|
||||
"s_k_0_1s_3_irasis",
|
||||
@ -398,9 +398,9 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Forest 1
|
||||
Pair(Episode.I, 1) to Fix(
|
||||
renderOnTopTextures = jsSetOf(12, 24, 41),
|
||||
renderOnTopTextures = unsafeSetOf(12, 24, 41),
|
||||
// Hiding trees and their shadows doesn't seem to improve things.
|
||||
// hiddenObjects = jsSetOf(
|
||||
// hiddenObjects = unsafeSetOf(
|
||||
// // Trees
|
||||
// "s_n_0_2i_2_im1teq",
|
||||
// "s_n_0_2i_2_im1tep",
|
||||
@ -425,8 +425,8 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Cave 1
|
||||
Pair(Episode.I, 3) to Fix(
|
||||
renderOnTopTextures = jsSetOf(89),
|
||||
hiddenObjects = jsSetOf(
|
||||
renderOnTopTextures = unsafeSetOf(89),
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_n_0_8_1_iqrqjj",
|
||||
"s_i_0_b5_1_is7ajh",
|
||||
"s_n_0_24_1_in5ce2",
|
||||
@ -463,7 +463,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Cave 2
|
||||
Pair(Episode.I, 4) to Fix(
|
||||
hiddenObjects = jsSetOf(
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_n_0_4j_1_irf90i",
|
||||
"s_n_0_5i_1_iqqrft",
|
||||
"s_n_0_g_1_iipv9r",
|
||||
@ -490,7 +490,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Cave 3
|
||||
Pair(Episode.I, 5) to Fix(
|
||||
hiddenObjects = jsSetOf(
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_n_0_2o_5_inun1c",
|
||||
"s_n_0_5y_2_ipyair",
|
||||
"s_n_0_6s_1_ineank",
|
||||
@ -540,7 +540,7 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Mine 1
|
||||
Pair(Episode.I, 6) to Fix(
|
||||
hiddenObjects = jsSetOf(
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_n_0_2e_2_iqfpg8",
|
||||
"s_n_0_d_1_iruof6",
|
||||
"s_n_0_o_1_im9ta5",
|
||||
@ -628,8 +628,8 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Mine 2
|
||||
Pair(Episode.I, 7) to Fix(
|
||||
renderOnTopTextures = jsSetOf(0, 1, 7, 17, 23, 56, 57, 58, 59, 60, 83),
|
||||
hiddenObjects = jsSetOf(
|
||||
renderOnTopTextures = unsafeSetOf(0, 1, 7, 17, 23, 56, 57, 58, 59, 60, 83),
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_n_0_22_4_imqetn",
|
||||
"s_n_0_25_4_imqeto",
|
||||
"s_n_0_26_4_imqeto",
|
||||
@ -719,8 +719,8 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Ruins 1
|
||||
Pair(Episode.I, 8) to Fix(
|
||||
renderOnTopTextures = jsSetOf(1, 21, 22, 27, 28, 43, 51, 59, 70, 72, 75),
|
||||
hiddenObjects = jsSetOf(
|
||||
renderOnTopTextures = unsafeSetOf(1, 21, 22, 27, 28, 43, 51, 59, 70, 72, 75),
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_n_0_2p_4_iohs6r",
|
||||
"s_n_0_2q_4_iohs6r",
|
||||
"s_m_0_l_1_io448k",
|
||||
@ -728,18 +728,18 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Ruins 2
|
||||
Pair(Episode.I, 9) to Fix(
|
||||
hiddenObjects = jsSetOf(
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_m_0_l_1_io448k",
|
||||
),
|
||||
),
|
||||
// Lab
|
||||
Pair(Episode.II, 0) to Fix(
|
||||
renderOnTopTextures = jsSetOf(36, 37, 38, 48, 60, 67, 79, 80),
|
||||
renderOnTopTextures = unsafeSetOf(36, 37, 38, 48, 60, 67, 79, 80),
|
||||
),
|
||||
// VR Spaceship Alpha
|
||||
Pair(Episode.II, 3) to Fix(
|
||||
renderOnTopTextures = jsSetOf(7, 59),
|
||||
hiddenObjects = jsSetOf(
|
||||
renderOnTopTextures = unsafeSetOf(7, 59),
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_l_0_45_5_ing07n",
|
||||
"s_n_0_45_5_ing07k",
|
||||
"s_n_0_g2_b_im2en1",
|
||||
@ -774,12 +774,12 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Central Control Area
|
||||
Pair(Episode.II, 5) to Fix(
|
||||
renderOnTopTextures = jsSetOf(*((0..59).toSet() + setOf(69, 77)).toTypedArray()),
|
||||
renderOnTopTextures = unsafeSetOf(*((0..59).toSet() + setOf(69, 77)).toTypedArray()),
|
||||
),
|
||||
// Jungle Area East
|
||||
Pair(Episode.II, 6) to Fix(
|
||||
renderOnTopTextures = jsSetOf(0, 1, 2, 18, 21, 24),
|
||||
hiddenObjects = jsSetOf(
|
||||
renderOnTopTextures = unsafeSetOf(0, 1, 2, 18, 21, 24),
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"a_m_0_1i_1_isf1hw",
|
||||
"a_m_0_1i_1_isfvf0",
|
||||
"a_m_0_1i_1_ise7ew",
|
||||
@ -802,8 +802,8 @@ class AreaAssetLoader(private val assetLoader: AssetLoader) : DisposableContaine
|
||||
),
|
||||
// Subterranean Desert 1
|
||||
Pair(Episode.IV, 6) to Fix(
|
||||
renderOnTopTextures = jsSetOf(48, 50, 58, 66, 80, 81, 92, 93, 94, 99, 100, 103),
|
||||
hiddenObjects = jsSetOf(
|
||||
renderOnTopTextures = unsafeSetOf(48, 50, 58, 66, 80, 81, 92, 93, 94, 99, 100, 103),
|
||||
hiddenObjects = unsafeSetOf(
|
||||
"s_v_f_16u_b_j2s5tx",
|
||||
"s_v_d_84_f_j046sf",
|
||||
"s_v_1v_205_2n_jb17vl",
|
||||
|
@ -1,6 +1,8 @@
|
||||
package world.phantasmal.webui.dom
|
||||
|
||||
import org.w3c.dom.HTMLElement
|
||||
import world.phantasmal.core.ResizeObserver
|
||||
import world.phantasmal.core.ResizeObserverEntry
|
||||
import world.phantasmal.core.unsafe.unsafeAssertNotNull
|
||||
import world.phantasmal.observable.ChangeEvent
|
||||
import world.phantasmal.observable.Dependent
|
||||
@ -9,20 +11,15 @@ import world.phantasmal.observable.cell.AbstractCell
|
||||
data class Size(val width: Double, val height: Double)
|
||||
|
||||
class HTMLElementSizeCell(element: HTMLElement? = null) : AbstractCell<Size>() {
|
||||
private var resizeObserver: dynamic = null
|
||||
private var resizeObserver: ResizeObserver? = null
|
||||
|
||||
private var _value: Size? = null
|
||||
|
||||
var element: HTMLElement? = element
|
||||
set(element) {
|
||||
if (resizeObserver != null) {
|
||||
if (field != null) {
|
||||
resizeObserver.unobserve(field)
|
||||
}
|
||||
|
||||
if (element != null) {
|
||||
resizeObserver.observe(element)
|
||||
}
|
||||
resizeObserver?.let { resizeObserver ->
|
||||
field?.let(resizeObserver::unobserve)
|
||||
element?.let(resizeObserver::observe)
|
||||
}
|
||||
|
||||
field = element
|
||||
@ -40,14 +37,10 @@ class HTMLElementSizeCell(element: HTMLElement? = null) : AbstractCell<Size>() {
|
||||
override fun addDependent(dependent: Dependent) {
|
||||
if (dependents.isEmpty()) {
|
||||
if (resizeObserver == null) {
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val resize = ::resizeCallback
|
||||
resizeObserver = js("new ResizeObserver(resize);")
|
||||
resizeObserver = ResizeObserver(::resizeCallback)
|
||||
}
|
||||
|
||||
if (element != null) {
|
||||
resizeObserver.observe(element)
|
||||
}
|
||||
element?.let(unsafeAssertNotNull(resizeObserver)::observe)
|
||||
|
||||
_value = getSize()
|
||||
}
|
||||
@ -59,7 +52,7 @@ class HTMLElementSizeCell(element: HTMLElement? = null) : AbstractCell<Size>() {
|
||||
super.removeDependent(dependent)
|
||||
|
||||
if (dependents.isEmpty()) {
|
||||
resizeObserver.disconnect()
|
||||
resizeObserver?.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,12 +65,9 @@ class HTMLElementSizeCell(element: HTMLElement? = null) : AbstractCell<Size>() {
|
||||
?.let { Size(it.offsetWidth.toDouble(), it.offsetHeight.toDouble()) }
|
||||
?: Size(0.0, 0.0)
|
||||
|
||||
private fun resizeCallback(entries: Array<dynamic>) {
|
||||
private fun resizeCallback(entries: Array<ResizeObserverEntry>) {
|
||||
val entry = entries.first()
|
||||
val newValue = Size(
|
||||
entry.contentRect.width.unsafeCast<Double>(),
|
||||
entry.contentRect.height.unsafeCast<Double>(),
|
||||
)
|
||||
val newValue = Size(entry.contentRect.width, entry.contentRect.height)
|
||||
|
||||
if (newValue != _value) {
|
||||
emitMightChange()
|
||||
|
Loading…
Reference in New Issue
Block a user