I'm working on creating an events system for my game, and my code currently looks like this:
export const enum ET { Collision, Dying, Damage }
type ActionCallback = (scene: Scene, event: GameEvent) => void;
subscribe(eventType: ET, callback: ActionCallback) {
this.subscriptions[eventType].push(callback);
}
And then an example of some code which uses this function is like this:
scene.events.subscribe(ET.Dying, handleEntityDeath);
handleEntityDeath = (scene: Scene, event: DyingEvent) => {
scene.deleteEntity(event.entity);
}
The problem is that TypeScript fails to compile and says something like: event's type must be GameEvent and not DyingEvent
.
Basically, I need a way to "link" ET.Dying
and DyingEvent
, but I'm not sure how to do it. I think if I can accomplish this, then I can event handlers like the above where it will only compile if the first parameter is something like ET.Dying
and the second parameter is a callback which takes a DyingEvent
. I would want it to fail to compile if the callback instead had a DamageEvent
parameter, if that makes sense.
Can this be done, and if so, how?
Figured it out:
interface EventMap {
[ET.Collision]: CollisionEvent;
[ET.Dying]: DyingEvent;
// etc
}
subscribe = <T extends ET>(eventType: T, callback: (scene: Scene, event: EventMap[T]) => void) => {
this.subscriptions[eventType].push(callback);
}
// Example calling code below here:
scene.events.subscribe(ET.Dying, handleEntityDeath);
handleEntityDeath = (scene: Scene, event: DyingEvent) => {
scene.deleteEntity(event.entity);
}
Edit: TS Playground example
First, create a new file: PubSub.ts
:
/**
* Defines the function type of the publish function.
*
* Extracts the keys from `E` as valid event types, and the matching
* property as the payload.
*/
type PubTypeFn<E> = <Key extends string & keyof E>(
event: Key,
message: E[Key]
) => void
/**
* Defines the function type for the subscribe function.
*
* Extracts the keys from `E` as valid event types, and the matching
* property as the payload to the callback function.
*/
type SubTypeFn<E> = <Key extends string & keyof E>(
event: Key,
fn: MessageFn<E>
) => void
/**
* Defines the function type for the subscription callback. Ensures
* the message payload is a valid property of the event being used.
*/
type MessageFn<E> = <Key extends string & keyof E>(message: E[Key]) => void
/**
* Tie everything together.
*/
type PubSubType<E> = {
publish: PubTypeFn<E>,
subscribe: SubTypeFn<E>
unsubscribe: SubTypeFn<E>
}
/**
* Creates a new PubSub instance, the `E` type parameter should be a
* type enumerating all the available events and their payloads.
*
* @example
* type Events = {
* warn: { message: string },
* error: { message: string }
* }
*
* const pubSub = PubSub<Events>()
* pubSub.publish('warn', { message: "Something bad happened!" })
*/
export function PubSub<E>(): PubSubType<E> {
const handlers: { [key: string]: (MessageFn<E>)[] } = {}
return {
publish: (event, msg) => {
handlers[event].forEach(h => h(msg))
},
subscribe: (event, callback) => {
const list = handlers[event] ?? []
list.push(callback)
handlers[event] = list
},
unsubscribe: (event, callback) => {
let list = handlers[event] ?? []
list = list.filter(h => h !== callback)
handlers[event] = list
}
}
}
Next, create another file where you will use the PubSub
func:
import { PubSub } from './PubSub'
type events = {
CreatedPerson: { id: string, name: string }
DeletedPerson: { personId: string; reason: string }
}
const pubSub = PubSub<events>()
pubSub.publish("CreatedPerson", { id: '1', name: 'cory' })
pubSub.subscribe("CreatedPerson", (message) => {
console.log(message)
})
ref:https://gist.github.com/sidola/3b267f21c872e449ef4bbdae9e2baeab
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.