From 35fbf93be77fd77da2abb26b1819439e46975b9c Mon Sep 17 00:00:00 2001 From: itsMapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Sun, 24 Jul 2022 15:02:07 -0500 Subject: [PATCH] trying to reduce "layers of conversion" one problem with the current iteration of reacord is the number of conversation layers there are between internals and the adapter. the flow is: elements -> node tree -> reacord objects -> adapter objects -> adapter renderer so far it looks like I can reduce this to: elements -> node tree -> adapter renderer --- packages/reacord/library.new/container.ts | 27 ++++++ packages/reacord/library.new/main.ts | 2 + packages/reacord/library.new/message-tree.ts | 9 -- packages/reacord/library.new/node.ts | 20 ++++ .../reacord/library.new/reacord-discord-js.ts | 95 ++++++++----------- .../library.new/reacord-instance-pool.ts | 38 ++++---- packages/reacord/library.new/reconciler.ts | 19 ++-- 7 files changed, 122 insertions(+), 88 deletions(-) create mode 100644 packages/reacord/library.new/container.ts delete mode 100644 packages/reacord/library.new/message-tree.ts create mode 100644 packages/reacord/library.new/node.ts diff --git a/packages/reacord/library.new/container.ts b/packages/reacord/library.new/container.ts new file mode 100644 index 0000000..75ce2c9 --- /dev/null +++ b/packages/reacord/library.new/container.ts @@ -0,0 +1,27 @@ +export class Container { + private items: T[] = [] + + getItems(): readonly T[] { + return this.items + } + + add(item: T) { + this.items.push(item) + } + + remove(item: T) { + const index = this.items.indexOf(item) + if (index === -1) return + this.items.splice(index, 1) + } + + clear() { + this.items = [] + } + + insertBefore(item: T, beforeItem: T) { + const index = this.items.indexOf(beforeItem) + if (index === -1) return + this.items.splice(index, 0, item) + } +} diff --git a/packages/reacord/library.new/main.ts b/packages/reacord/library.new/main.ts index d68e4f6..c9f7da3 100644 --- a/packages/reacord/library.new/main.ts +++ b/packages/reacord/library.new/main.ts @@ -1,3 +1,5 @@ +export { Button, type ButtonProps } from "./button" +export { type ButtonSharedProps } from "./button-shared-props" export { ReacordDiscordJs } from "./reacord-discord-js" export { type ReacordInstance, diff --git a/packages/reacord/library.new/message-tree.ts b/packages/reacord/library.new/message-tree.ts deleted file mode 100644 index badb0d3..0000000 --- a/packages/reacord/library.new/message-tree.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type MessageTree = { - children: TextNode[] - render: () => void -} - -export type TextNode = { - type: "text" - text: string -} diff --git a/packages/reacord/library.new/node.ts b/packages/reacord/library.new/node.ts new file mode 100644 index 0000000..275f376 --- /dev/null +++ b/packages/reacord/library.new/node.ts @@ -0,0 +1,20 @@ +export type Node = { + readonly type: string + readonly props?: Record + children?: Node[] + getText?: () => string +} + +export class TextNode implements Node { + readonly type = "text" + + constructor(private text: string) {} + + getText() { + return this.text + } + + setText(text: string) { + this.text = text + } +} diff --git a/packages/reacord/library.new/reacord-discord-js.ts b/packages/reacord/library.new/reacord-discord-js.ts index 6938e63..c98ab72 100644 --- a/packages/reacord/library.new/reacord-discord-js.ts +++ b/packages/reacord/library.new/reacord-discord-js.ts @@ -8,7 +8,11 @@ import type { } from "discord.js" import type { ReactNode } from "react" import { AsyncQueue } from "./async-queue" -import type { ReacordOptions } from "./reacord-instance-pool" +import type { Node } from "./node" +import type { + ReacordMessageRenderer, + ReacordOptions, +} from "./reacord-instance-pool" import { ReacordInstancePool } from "./reacord-instance-pool" export class ReacordDiscordJs { @@ -19,36 +23,8 @@ export class ReacordDiscordJs { } send(channelId: string, initialContent?: ReactNode) { - const renderer = new MessageRenderer() - - return this.instances.create({ - initialContent, - update: async (tree) => { - try { - const messageOptions: MessageOptions & MessageEditOptions = { - content: tree.children.map((child) => child.text).join(""), - } - const channel = await getTextChannel(this.client, channelId) - 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) - } - }, - }) + const renderer = new ChannelMessageRenderer(this.client, channelId) + return this.instances.create({ initialContent, renderer }) } reply(interaction: Interaction, initialContent?: ReactNode) {} @@ -56,22 +32,34 @@ export class ReacordDiscordJs { ephemeralReply(interaction: Interaction, initialContent?: ReactNode) {} } -class MessageRenderer { +class ChannelMessageRenderer implements ReacordMessageRenderer { private message: Message | undefined + private channel: TextBasedChannel | undefined private active = true private readonly queue = new AsyncQueue() - update( - options: MessageOptions & MessageEditOptions, - channel: TextBasedChannel, - ) { + constructor( + private readonly client: Client, + private readonly channelId: string, + ) {} + + update(nodes: readonly Node[]) { + const options: MessageOptions & MessageEditOptions = { + content: nodes.map((node) => node.getText?.() || "").join(""), + } + return this.queue.add(async () => { - if (!this.active) return + if (!this.active) { + return + } + if (this.message) { await this.message.edit(options) - } else { - this.message = await channel.send(options) + return } + + const channel = await this.getChannel() + this.message = await channel.send(options) }) } @@ -88,21 +76,22 @@ class MessageRenderer { // TODO: disable message components }) } -} -async function getTextChannel( - client: Client, - channelId: string, -): Promise { - const channel = - client.channels.cache.get(channelId) ?? - (await client.channels.fetch(channelId)) + private async getChannel() { + if (this.channel) { + return this.channel + } - if (!channel) { - throw new Error(`Channel ${channelId} not found`) + const channel = + this.client.channels.cache.get(this.channelId) ?? + (await this.client.channels.fetch(this.channelId)) + + if (!channel) { + throw new Error(`Channel ${this.channelId} not found`) + } + if (!channel.isTextBased()) { + throw new Error(`Channel ${this.channelId} is not a text channel`) + } + return (this.channel = channel) } - if (!channel.isTextBased()) { - throw new Error(`Channel ${channelId} is not a text channel`) - } - return channel } diff --git a/packages/reacord/library.new/reacord-instance-pool.ts b/packages/reacord/library.new/reacord-instance-pool.ts index c30485b..1d5ddec 100644 --- a/packages/reacord/library.new/reacord-instance-pool.ts +++ b/packages/reacord/library.new/reacord-instance-pool.ts @@ -1,5 +1,6 @@ import type { ReactNode } from "react" -import type { MessageTree } from "./message-tree" +import { Container } from "./container" +import type { Node } from "./node" import { reconciler } from "./reconciler" export type ReacordOptions = { @@ -26,7 +27,11 @@ export type ReacordInstance = { export type ReacordInstanceOptions = { initialContent: ReactNode - update: (tree: MessageTree) => Promise + renderer: ReacordMessageRenderer +} + +export type ReacordMessageRenderer = { + update: (nodes: readonly Node[]) => Promise deactivate: () => Promise destroy: () => Promise } @@ -39,20 +44,19 @@ export class ReacordInstancePool { this.options = { maxInstances } } - create(options: ReacordInstanceOptions) { - const tree: MessageTree = { - children: [], - render: async () => { - try { - await options.update(tree) - } catch (error) { - console.error("Failed to update message.", error) - } - }, + create({ initialContent, renderer }: ReacordInstanceOptions) { + const nodes = new Container() + + const render = async () => { + try { + await renderer.update(nodes.getItems()) + } catch (error) { + console.error("Failed to update message.", error) + } } const container = reconciler.createContainer( - tree, + { nodes, render }, 0, // eslint-disable-next-line unicorn/no-null null, @@ -72,7 +76,7 @@ export class ReacordInstancePool { deactivate: async () => { this.instances.delete(instance) try { - await options.deactivate() + await renderer.deactivate() } catch (error) { console.error("Failed to deactivate message.", error) } @@ -80,15 +84,15 @@ export class ReacordInstancePool { destroy: async () => { this.instances.delete(instance) try { - await options.destroy() + await renderer.destroy() } catch (error) { console.error("Failed to destroy message.", error) } }, } - if (options.initialContent !== undefined) { - instance.render(options.initialContent) + if (initialContent !== undefined) { + instance.render(initialContent) } if (this.instances.size > this.options.maxInstances) { diff --git a/packages/reacord/library.new/reconciler.ts b/packages/reacord/library.new/reconciler.ts index 8251ad2..53e00fa 100644 --- a/packages/reacord/library.new/reconciler.ts +++ b/packages/reacord/library.new/reconciler.ts @@ -1,11 +1,13 @@ import ReactReconciler from "react-reconciler" import { DefaultEventPriority } from "react-reconciler/constants" -import type { MessageTree, TextNode } from "./message-tree" +import type { Container } from "./container" +import type { Node } from "./node" +import { TextNode } from "./node" export const reconciler = ReactReconciler< string, // Type Record, // Props - MessageTree, // Container + { nodes: Container; render: () => void }, // Container never, // Instance TextNode, // TextInstance never, // SuspenseInstance @@ -30,7 +32,7 @@ export const reconciler = ReactReconciler< }, createTextInstance(text) { - return { type: "text", text } + return new TextNode(text) }, appendInitialChild(parent, child) {}, @@ -38,28 +40,27 @@ export const reconciler = ReactReconciler< appendChild(parentInstance, child) {}, appendChildToContainer(container, child) { - container.children.push(child) + container.nodes.add(child) }, insertBefore(parentInstance, child, beforeChild) {}, insertInContainerBefore(container, child, beforeChild) { - const index = container.children.indexOf(beforeChild) - if (index !== -1) container.children.splice(index, 0, child) + container.nodes.insertBefore(child, beforeChild) }, removeChild(parentInstance, child) {}, removeChildFromContainer(container, child) { - container.children = container.children.filter((c) => c !== child) + container.nodes.remove(child) }, clearContainer(container) { - container.children = [] + container.nodes.clear() }, commitTextUpdate(textInstance, oldText, newText) { - textInstance.text = newText + textInstance.setText(newText) }, commitUpdate(