classes are fine, actually! + simplified things more

This commit is contained in:
itsMapleLeaf
2022-07-24 13:39:55 -05:00
parent 533d8a0f60
commit f9564897aa
4 changed files with 135 additions and 148 deletions

View File

@@ -1,27 +1,25 @@
export type AsyncCallback = () => unknown export type AsyncCallback = () => unknown
export function createAsyncQueue() { export class AsyncQueue {
const callbacks: AsyncCallback[] = [] private callbacks: AsyncCallback[] = []
let promise: Promise<void> | undefined private promise: Promise<void> | undefined
async function add(callback: AsyncCallback) { async add(callback: AsyncCallback) {
callbacks.push(callback) this.callbacks.push(callback)
if (promise) return promise if (this.promise) return this.promise
promise = runQueue() this.promise = this.runQueue()
try { try {
await promise await this.promise
} finally { } finally {
promise = undefined this.promise = undefined
} }
} }
async function runQueue() { private async runQueue() {
let callback: AsyncCallback | undefined let callback: AsyncCallback | undefined
while ((callback = callbacks.shift())) { while ((callback = this.callbacks.shift())) {
await callback() await callback()
} }
} }
return { add }
} }

View File

@@ -7,82 +7,97 @@ import type {
TextBasedChannel, TextBasedChannel,
} from "discord.js" } from "discord.js"
import type { ReactNode } from "react" import type { ReactNode } from "react"
import { createAsyncQueue } from "./async-queue" import { AsyncQueue } from "./async-queue"
import type { ReacordOptions } from "./reacord" import type { ReacordOptions } from "./reacord"
import { createReacordInstanceManager } from "./reacord" import { ReacordInstancePool } from "./reacord"
export class ReacordDiscordJs {
private instances
constructor(private readonly client: Client, options: ReacordOptions = {}) {
this.instances = new ReacordInstancePool(options)
}
export function createReacordDiscordJs(
client: Client,
options: ReacordOptions = {},
) {
const manager = createReacordInstanceManager(options)
return {
send(channelId: string, initialContent?: ReactNode) { send(channelId: string, initialContent?: ReactNode) {
const handler = createMessageHandler() const renderer = new MessageRenderer()
return manager.createInstance({
return this.instances.create({
initialContent, initialContent,
update: async (tree) => { update: async (tree) => {
try {
const messageOptions: MessageOptions & MessageEditOptions = { const messageOptions: MessageOptions & MessageEditOptions = {
content: tree.children.map((child) => child.text).join(""), content: tree.children.map((child) => child.text).join(""),
} }
const channel = await getTextChannel(client, channelId) const channel = await getTextChannel(this.client, channelId)
await handler.update(messageOptions, channel) await renderer.update(messageOptions, channel)
} catch (error) {
console.error("Error updating message:", error)
}
},
destroy: async () => {
try {
await renderer.destroy()
} catch (error) {
console.error("Error destroying message:", error)
}
},
deactivate: async () => {
try {
await renderer.deactivate()
} catch (error) {
console.error("Error deactivating message:", error)
}
}, },
destroy: () => handler.destroy(),
deactivate: () => handler.deactivate(),
}) })
},
reply(interaction: Interaction, initialContent?: ReactNode) {},
ephemeralReply(interaction: Interaction, initialContent?: ReactNode) {},
}
} }
function createMessageHandler() { reply(interaction: Interaction, initialContent?: ReactNode) {}
let message: Message | undefined
let active = true
const queue = createAsyncQueue()
async function update( ephemeralReply(interaction: Interaction, initialContent?: ReactNode) {}
}
class MessageRenderer {
private message: Message | undefined
private active = true
private readonly queue = new AsyncQueue()
update(
options: MessageOptions & MessageEditOptions, options: MessageOptions & MessageEditOptions,
channel: TextBasedChannel, channel: TextBasedChannel,
) { ) {
return queue.add(async () => { return this.queue.add(async () => {
if (!active) return if (!this.active) return
if (message) { if (this.message) {
await message.edit(options) await this.message.edit(options)
} else { } else {
message = await channel.send(options) this.message = await channel.send(options)
} }
}) })
} }
async function destroy() { destroy() {
return queue.add(async () => { return this.queue.add(async () => {
active = false this.active = false
await message?.delete() await this.message?.delete()
}) })
} }
async function deactivate() { deactivate() {
return queue.add(async () => { return this.queue.add(async () => {
active = false this.active = false
// TODO: disable message components // TODO: disable message components
}) })
} }
return { update, destroy, deactivate }
} }
async function getTextChannel( async function getTextChannel(
client: Client<boolean>, client: Client<boolean>,
channelId: string, channelId: string,
): Promise<TextBasedChannel> { ): Promise<TextBasedChannel> {
let channel = client.channels.cache.get(channelId) const channel =
if (!channel) { client.channels.cache.get(channelId) ??
channel = (await client.channels.fetch(channelId)) ?? undefined (await client.channels.fetch(channelId))
}
if (!channel) { if (!channel) {
throw new Error(`Channel ${channelId} not found`) throw new Error(`Channel ${channelId} not found`)
} }

View File

@@ -1,2 +1,2 @@
export { createReacordDiscordJs } from "./discord-js" export { ReacordDiscordJs } from "./discord-js"
export { type ReacordInstance, type ReacordOptions } from "./reacord" export { type ReacordInstance, type ReacordOptions } from "./reacord"

View File

@@ -24,56 +24,29 @@ export type ReacordInstance = {
deactivate: () => void deactivate: () => void
} }
type ReacordInstanceOptions = { export type ReacordInstanceOptions = {
initialContent: ReactNode initialContent: ReactNode
update: (tree: MessageTree) => unknown update: (tree: MessageTree) => Promise<void>
deactivate: () => unknown deactivate: () => Promise<void>
destroy: () => unknown destroy: () => Promise<void>
} }
export function createReacordInstanceManager({ export class ReacordInstancePool {
maxInstances = 50, private readonly options: Required<ReacordOptions>
}: ReacordOptions) { private readonly instances = new Set<ReacordInstance>()
const instances: ReacordInstance[] = []
function createInstance(options: ReacordInstanceOptions) { constructor({ maxInstances = 50 }: ReacordOptions) {
const instance = createReacordInstance({ this.options = { maxInstances }
...options,
deactivate() {
instances.splice(instances.indexOf(instance), 1)
return options.deactivate()
},
destroy() {
instances.splice(instances.indexOf(instance), 1)
return options.destroy()
},
})
instances.push(instance)
if (instances.length > maxInstances) {
instances.shift()?.deactivate()
} }
return instance create(options: ReacordInstanceOptions) {
}
return { createInstance }
}
function createReacordInstance(
options: ReacordInstanceOptions,
): ReacordInstance {
const tree: MessageTree = { const tree: MessageTree = {
children: [], children: [],
render: async () => { render: async () => {
try { try {
await options.update(tree) await options.update(tree)
} catch (error) { } catch (error) {
console.error( console.error("Failed to update message.", error)
"Reacord encountered an error while updating the message.",
error,
)
} }
}, },
} }
@@ -93,27 +66,23 @@ function createReacordInstance(
) )
const instance: ReacordInstance = { const instance: ReacordInstance = {
render(content: ReactNode) { render: (content: ReactNode) => {
reconciler.updateContainer(content, container) reconciler.updateContainer(content, container)
}, },
async deactivate() { deactivate: async () => {
this.instances.delete(instance)
try { try {
await options.deactivate() await options.deactivate()
} catch (error) { } catch (error) {
console.error( console.error("Failed to deactivate message.", error)
"Reacord encountered an error while deactivating an instance.",
error,
)
} }
}, },
async destroy() { destroy: async () => {
this.instances.delete(instance)
try { try {
await options.destroy() await options.destroy()
} catch (error) { } catch (error) {
console.error( console.error("Failed to destroy message.", error)
"Reacord encountered an error while destroying an instance.",
error,
)
} }
}, },
} }
@@ -122,5 +91,10 @@ function createReacordInstance(
instance.render(options.initialContent) instance.render(options.initialContent)
} }
if (this.instances.size > this.options.maxInstances) {
;[...this.instances][0]?.deactivate()
}
return instance return instance
} }
}