Provide a hooks based API alternative (possibly replace the implementation of the class api with a hooks implementation, while keeping the actual api the same).
Part 1: The API
import { dynamicMessage, useReaction, useState } from 'discord-dynamic-messages';
const ToggleMessage = dynamicMessage(() => {
const state = useState({
toggle: false,
});
const reaction = useReaction(':white_check_mark:');
reaction.show();
reaction.on('added', () => {
state.toggle = true;
});
reaction.on('removed', () => {
state.toggle = false;
});
return `Toggle: ${state.toggle}`;
});
/*
ToggleMessage().sendTo(message.channel);
*/
import { dynamicMessage, useReaction, useState } from 'discord-dynamic-messages';
interface CountMessageParams {
initialCount: number;
}
interface State {
count: number;
}
const CountMessage = dynamicMessage<CountMessageParams>(({ initialCount }) => {
const state = useState<State>({
count: initialCount,
});
const reaction = useReaction(':thumbsup:');
reaction.show();
reaction.on('added', ({ user }) => {
state.count += 1;
reaction.remove(user);
});
return `Counter: ${state.count}`;
});
/*
CountMessage({
initialCount: 3,
}).sendTo(message.channel);
*/
import { dynamicMessage, onAttached, onDetached } from 'discord-dynamic-messages';
const LifeCycleEvents = dynamicMessage(() => {
onAttached((message) => {
// Will trigger both when sending a new message
// as well as when attaching to an existing message.
console.log(`Attached to message: ${message.id}`);
});
onDetached(() => {
// Will trigger when a the attached message is no longer available.
// either when the message is deleted, or when the dynamicMessage
// is purposefully detached.
console.log(`Detached from message: ${message.id}`);
});
return `Logging....`;
});
/*
LifeCycleEvents().sendTo(message.channel);
*/
import { dynamicMessage, useSelf, useState } from 'discord-dynamic-messages';
const MetaManimulationMessage = dynamicMessage(() => {
const self = useSelf();
// self === MetaManimulationMessage
const state = useState({
timeLeft: 10,
});
setInterval(() => {
state.timeLeft -= 1;
}, 1000);
if (state.timeLeft <= 0) {
self.delete();
return `Goodbye!`;
}
return `Message will self destruct in ${state.timeLeft}`;
});
/*
MetaManimulationMessage().sendTo(message.channel);
*/
Part 2: Minimizing updates
Lets assume we have a message like the following:
import { dynamicMessage } from 'discord-dynamic-messages';
const HelloWorldMessage = dynamicMessage(() => {
return `Hello World`;
});
/*
HelloWorldMessage().sendTo(message.channel);
*/
It doesn't use any reactive hooks and can there for be optimized heavily. We will only need to call the setup function once. We can then store the result in a cache and re-use the cached value whenever an instance of HelloWorldMessage
is created or attached.
Consider a slightly more complex case:
import { dynamicMessage, useReaction } from 'discord-dynamic-messages';
const ReactionMessage = dynamicMessage(() => {
const reaction = useReaction(':thumbsup:');
reaction.on('added', () => {
console.log('Reaction made');
});
reaction.show();
return `Log`;
});
This message adds a reaction handler, however since no state is being kept in the message, this setup function can be optimized to only run once per instance of ReactionMessage
.
Now lets introduce instance specific state:
import { dynamicMessage, useState, useReaction } from 'discord-dynamic-messages';
const ReactionMessage = dynamicMessage(() => {
const state = useState({
count: 0,
});
const reaction = useReaction(':thumbsup:');
reaction.on('added', () => {
state.count += 1;
});
reaction.show();
return `Count: ${state.count}`;
});
In this case the setup function is going to have to run again every time a property on the state object changes. This introduces two possible performance bottlenecks, 1) since not all state changes necessarily ends up result in a new message body, we are going to keep a local cash of the previous message body, and compare it to the new message body. Then we will update the underlying discord message, If and only if the previous and the new message body are different. 2) since we are calling reaction.show()
every time we run the setup function, we need to keep track of if the reaction was "showed" in the last update or not. If it was then we don't have to update the message, however if it wasn't we need to update it. (grammar.... wow... I'll go through this later)
Part 3: Mutations & Transactions
const fakeCTX = ({
commit: (transaction) => {
console.log(transaction);
}
});
const getContext = () => fakeCTX;
const internalState = new WeakMap();
// Pseudo code for the useReaction logic
const useReaction = (emoji) => {
const ctx = getContext();
if (!internalState.has(ctx)) {
internalState.set(ctx, {
visable: false,
});
}
const state = internalState.get(ctx);
const api = {
show: () => {
if (state.visable) {
// Already visable
return;
}
internalState.set(ctx, {
...state,
visable: true,
});
ctx.commit({
subject: 'reacton',
operation: 'add',
arguments: [ emoji ]
});
}
};
return api;
}
const setup = () => {
const reaction = useReaction(':thumbsup:');
reaction.show();
}
setup();
setup();
setup();