Uncaught RuntimeError: memory access out of bounds Error

Hey I am getting this error when tracking the velocity of my entity. It is causing things to crash. Here is the state that is causing this error and crash:

ecs.defineState('moving')
      .onEnter(() => {
        ApplyForce()
      })
      .onTick(() => {
        ecs.physics.getLinearVelocity(world, dataAttribute.cursor(eid).player)
      })
  },

Here is the full error message:

Uncaught RuntimeError: memory access out of bounds
at void std::__2::__tree_balance_after_insert<std::__2::__tree_node_base<void*>>(std::__2::__tree_node_base<void>, std::__2::__tree_node_base<void>*) 003e03aa:wasm-function[65]:0x2b11
at handleObserveEvent 003e03aa:wasm-function[64]:0x29b5
at flecs_observer_invoke 003e03aa:wasm-function[1270]:0x82c52
at flecs_multi_observer_invoke 003e03aa:wasm-function[1271]:0x83154
at flecs_multi_observer_builtin_run 003e03aa:wasm-function[1275]:0x847e2
at flecs_observer_invoke 003e03aa:wasm-function[1270]:0x82c52
at flecs_observers_invoke 003e03aa:wasm-function[1267]:0x82b08
at flecs_emit 003e03aa:wasm-function[1261]:0x818ef
at flecs_notify_on_set 003e03aa:wasm-function[1085]:0x73365
at ecs_modified_id 003e03aa:wasm-function[1118]:0x763be

Is this a bad way to track the velocity? I need to know when my velocity is both past a certain speed and at 0. Those two factors change the state of the 3D model so they are kind of important.

It may be because I have onTick running in multiple scripts simultaneously. Is there a best practice for this? Here are my 4 current scripts

Camera Controller

import * as ecs from '@8thwall/ecs'  // This is how you access the ecs library.

ecs.registerComponent({
  name: 'camera-controller',
  schema: {
    // Add data that can be configured on the component.
    playerEntity: ecs.eid,  // this is the Player
  },
  schemaDefaults: {
    // Add defaults for the schema fields.
  },
  data: {
    // Add data that cannot be configured outside of the component.
  },
  stateMachine: ({world, eid, dataAttribute, schemaAttribute}) => {
    ecs.defineState('inGame').initial()
      .onTick(() => {
        const PlayerPositionY = ecs.Position.get(world,
          schemaAttribute.cursor(eid).playerEntity).y
        const cameraPosition = PlayerPositionY + 7
        ecs.Position.mutate(world, eid, (cursor) => {
          cursor.y = cameraPosition
        })
      })
  },
  add: (world, component) => {
    // Runs when the component is added to the world.
  },
  tick: (world, component) => {
    // Runs every frame.
  },
  remove: (world, component) => {
    // Runs when the component is removed from the world.
  },
})

Player Controller

import * as ecs from '@8thwall/ecs'  // This is how you access the ecs library.

ecs.registerComponent({
  name: 'player-controller',
  schema: {
    // Add data that can be configured on the component.
  },
  schemaDefaults: {
    // Add defaults for the schema fields.
  },
  data: {
    // Add data that cannot be configured outside of the component.
  },
  add: (world, component) => {
    // Runs when the component is added to the world.
  },
  stateMachine: ({world, eid, dataAttribute, schemaAttribute}) => {
    // Active state: When the expereince is active
    ecs.defineState('Standing').initial()
    ecs.defineState('Squating')
    ecs.defineState('Jumping')
    ecs.defineState('Falling')
    ecs.defineState('Laying')
  },
  tick: (world, component) => {
    // Runs every frame.
  },
  remove: (world, component) => {
    // Runs when the component is removed from the world.
  },
})

Jump Controller

import * as ecs from '@8thwall/ecs'  // This is how you access the ecs library.

ecs.registerComponent({
  name: 'jump-controller',
  schema: {
    jumpForce: ecs.f32,  // this is the force we use to scale the jump power
    // @label Z Rotation Starting Point (in Degrees)
    zRotationStart: ecs.f32,  // this is the starting z place of the rotation
    // @label Z Rotation Finishing Point (in Degrees)
    zRotationFinish: ecs.f32,  // this is the starting z place of the rotation
    // @label Z Rotation Duration (in seconds)
    zRotationDuration: ecs.f32,  // this is the starting z place of the rotation
    inputController: ecs.eid,  // this is the input controller entity
  },
  schemaDefaults: {
    jumpForce: 20,  // this is the default value of jump force
    zRotationStart: 30,  // this is the default value of jump force
    zRotationFinish: 150,  // this is the default value of jump force
    zRotationDuration: 1.5,  // this is the default value of jump force

  },
  data: {
    movingLeft: ecs.boolean,  // this is for the setting angle tick
    rotationPerMilisecond: ecs.f32,  // this is the amount we rotate by mer milisecond
    previousZRotation: ecs.f32,  // this is the previous z rotation
    jumpZAngle: ecs.f32,  // this is the value of the z jump angle after set
    jumpForceFinal: ecs.f32,  // this is the final jump force value
    player: ecs.eid,  // this is the player
  },
  stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
    const setValue = ecs.defineTrigger()
    const SetAngle = () => {
      dataAttribute.cursor(eid).jumpZAngle = dataAttribute.cursor(eid).previousZRotation
      setValue.trigger()
    }
    const SetPower = () => {
      dataAttribute.cursor(eid).jumpForceFinal = 1 * schemaAttribute.cursor(eid).jumpForce
      setValue.trigger()
    }
    const ApplyForce = () => {
      const horizontalForce = Math.cos(dataAttribute.cursor(eid).jumpZAngle *
      (Math.PI / 180)) * dataAttribute.cursor(eid).jumpForceFinal
      const verticalForce = Math.sin(dataAttribute.cursor(eid).jumpZAngle *
      (Math.PI / 180)) * dataAttribute.cursor(eid).jumpForceFinal
      ecs.physics.applyImpulse(world, dataAttribute.cursor(eid).player,
        horizontalForce, verticalForce, 0)
    }
    ecs.defineState('idle')
    ecs.defineState('settingAngle').initial()
      .onTrigger(setValue, 'settingPower')
      .listen(schemaAttribute.cursor(eid).inputController, 'Submit', SetAngle)
      .onEnter(() => {
        dataAttribute.cursor(eid).previousZRotation =
      schemaAttribute.cursor(eid).zRotationStart
        dataAttribute.cursor(eid).movingLeft = true
      })
      .onTick(() => {
        const {delta} = world.time  // Get the time delta for smooth movement
        let newZRotation  // this is the z rotation in degrees
        if (dataAttribute.cursor(eid).movingLeft === true) {
          newZRotation = dataAttribute.cursor(eid).previousZRotation +
          (delta * dataAttribute.cursor(eid).rotationPerMilisecond)
          if (newZRotation >= schemaAttribute.cursor(eid).zRotationFinish) {
            dataAttribute.cursor(eid).movingLeft = false
          }
        } else {
          newZRotation = dataAttribute.cursor(eid).previousZRotation -
          (delta * dataAttribute.cursor(eid).rotationPerMilisecond)
          if (newZRotation <= schemaAttribute.cursor(eid).zRotationStart) {
            dataAttribute.cursor(eid).movingLeft = true
          }
        }
        const newQuatRotaion = ecs.math.quat.zDegrees(newZRotation)  // this is a Quaternion
        ecs.Quaternion.set(world, eid, newQuatRotaion)
        dataAttribute.cursor(eid).previousZRotation = newZRotation
      })
    ecs.defineState('settingPower')
      .onTrigger(setValue, 'moving')
      .listen(schemaAttribute.cursor(eid).inputController, 'Submit', SetPower)

    ecs.defineState('moving')
      .onEnter(() => {
        ApplyForce()
      })
      .onTick(() => {
        console.log(ecs.physics.getLinearVelocity(world, dataAttribute.cursor(eid).player).y)
      })
  },
  add: (world, component) => {
    component.data.movingLeft = true
    const rotationDistance = component.schema.zRotationFinish - component.schema.zRotationStart
    const actualDuration = component.schema.zRotationDuration * 1000
    const rotationPerMilisecond = rotationDistance / actualDuration
    component.data.rotationPerMilisecond = rotationPerMilisecond
    component.data.player = world.getParent(component.eid)
  },
  tick: (world, component) => {
  },
  remove: (world, component) => {
  },
})

Input Controller

import * as ecs from '@8thwall/ecs'  // This is how you access the ecs library.

// Register the InputController component
ecs.registerComponent({

  name: 'input-controller',
  schema: {
    // UI buttons for various actions
    // submitButton: ecs.eid,       // Entity ID for UI button submit
  },
  data: {
    buttonClicked: ecs.boolean,  //
  },

  // Define the state machine for handling input during different game phases
  stateMachine: ({world, eid, dataAttribute, schemaAttribute}) => {
    // Function to handle button input
    const handleClick = (buttonName) => {
      if (dataAttribute.cursor(eid).buttonClicked === false) {
        world.events.dispatch(eid, buttonName)  // Dispatch button Clicked event
        dataAttribute.cursor(eid).buttonClicked = true
        console.log(`clicked ${buttonName}`)
      }
    }

    // Active state: When the expereince is active
    ecs.defineState('inGame').initial()
      .onTick(() => {
        if (world.input.getAction('Submit')) handleClick('Submit')
        else dataAttribute.cursor(eid).buttonClicked = false
      })
      // .listen(schemaAttribute.cursor(eid).submitButton,
      //   ecs.input.SCREEN_TOUCH_START, () => handleClick('Submit'))
  },
})

Ok, after removing the tick lifecycle method from all of my scripts I stopped getting that error. I would still be interested in a potential best practice for tick mins and maxes.

Never mind, it still occurs. Not every time but it does still happen.

@christoph_nia maybe you have some input?

If anyone else wants to look at this here is a link to the project in its current state. Beware, the project is currently broken.

I did figure out a little work around. I now only check velocity every .1 seconds instead of on every tick. that seems to help. I think it has to do with the frequency that I am checking velocity. that is a good temp work around.

Using .cursor to read data is not exactly proper. I would recommend dataAttribute.get.

Ticks do not run simultaneously since Javascript is single-threaded.

From the stack trace you posted, it’s not clear which function call is failing.

Something like:

try {
  const {player} = dataAttribute.get(eid)
  try {
     ecs.physics.getLinearVelocity(world, player)
  } catch (err) {
    console.error('Failed to get velocity', err)
  }
} catch (err) {
  console.error('Failed to access data', err)
}

Will help narrow things down.

If that doesn’t have the error, maybe try switching back to .cursor(eid) to see if that does.

I tried that out with get and cursor and it was not catching but the same error was popping up.

here is my current fix that seems to be helping. I haven’t had an error since adding it.

 .onTick(() => {
        dataAttribute.get(eid).timeSinceLastUpdate += world.time.delta
        if (dataAttribute.cursor(eid).timeSinceLastUpdate >= 100) {
          const velocity = ecs.physics.getLinearVelocity(world, dataAttribute.get(eid).player)
          if (velocity.x === 0 && velocity.y < 0.001 && velocity.y >= 0) Grounded()
          dataAttribute.cursor(eid).timeSinceLastUpdate = 0
        }
      })

It may not be a long term solution but for now is working.

That means the error is actually coming from somewhere else

That must mean some other function is throwing the error. I’ll dig a little deeper.

here is my current fix that seems to be helping

I’m happy it’s working but check out this version:

const data = dataAttribute.acquire(eid)

data.timeSinceLastUpdate += world.time.delta

if (data.timeSinceLastUpdate >= 100 ) {
  const velocity = ecs.physics.getLinearVelocity(data.player)
  if (velocity.x === 0 && velocity.y < 0.001 && velocity.y >= 0) {
    Grounded()
  }
  data.timeSinceLastUpdate = 0
}

dataAttribute.commit()

This version is bit better because we acquire the data cursor once, then use it for reads and writes, then commit it. Using this pattern more generally across your code will likely make it more stable.

I thought that adding in const data = dataAttribute.acquire(eid) to a state machine effected the ability to use a component on multiple entities. In the past if i did that it would break a component that was on more than 1 entity simultaneously.

As long as commit is always called before the next time a cursor is attempted to be raised is made, it should be safe.

My initial code didn’t handle this exactly right either.

If my next onEnter callback when Grounded() is called, that might have conflicts. To fix this, I moved the Grounded() call after the commit:

const data = dataAttribute.acquire(eid)

data.timeSinceLastUpdate += world.time.delta

let isGrounded = false

if (data.timeSinceLastUpdate >= 100) {
  const velocity = ecs.physics.getLinearVelocity(world, data.player)
  if (velocity.x === 0 && velocity.y < 0.001 && velocity.y >= 0) {
    isGrounded = true
  }
  data.timeSinceLastUpdate = 0
}

dataAttribute.commit(eid)

// Call this after commit() so that when we switch to the other state, 
// we've already released the cursor
if (isGrounded) {
  Grounded()
}

I’ve also located the cause of the memory corruption which will be resolved in the next release (possibly today)

There was nothing actually wrong with your code, calling getLinearVelocity as many times as you did just triggered a bad case.

Oh, awesome!

All that you explained makes total sense. I appreciate all the help on this. I’m excited for the release!