#Statefulness and data sharing for Pieces

1 messages · Page 1 of 1 (latest)

acoustic forge
#

Hi,
let's imagine the following: you want to keep track of some data about the people who join and leave a certain voice channel. It would be convenient to store this data alongside the listener and maybe provide an interface to it directly from the listener.
However, it's not fully clear how and when Pieces are instantiated. There are no examples of holding (let alone sharing) any state. How to do this right?

  1. is it okay to hold state in a Listener instance?
  2. if so, how to access the very same instance somewhere else?
  3. if not, what to do instead? Exposing all that data via the container sounds quite painful because that's very far from encapsulated. What would you do to expose some data managed by a certain event handler?
summer ploverBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> ✅ Mark Solution

terse socket
#

use a database like sqlite, postgres, mongo, etc. Especially if you want to retain it between reboots. Otherwise, remember how DJS and sapphire do caching internally:

// store.ts
import { Collection } from 'discord.js';
import { container } from '@sapphire/framework';

export const myCache = new Collection<KeyType, ValueType>();
container.myCache = myCache;

declare module '@sapphire/framework' {
  interface Container {
    myCache: typeof myCache;
  }
}

// listener
this.container.myCache.get('id');
this.container.myCache.set('id', value);
// etc
#

as for when listeners are instantiated, once, before client login.

acoustic forge
#

Whether to use a database or not should also rather be an implementation detail; allowing anyone to just grab the database would be a lot like shared global variables

#

"Worst case" that should still be a container service but it really would be best to keep this the business of those 2 or 3 classes inside the same "module" (registered path)

primal lava
#

Most people just attach properties to container (accessible in pieces with this.container and importable elsewhere

#

For example, I have this code in my main function

  container.redis = redisClient;
  container.cardCache = new Map<string, any>();

And then I can access redis from calling this.container.redis.whatever, same for cardCache

acoustic forge
#

Yeah well, this is the point: this is better than nothing but in a sufficiently complex bot - which this will become over time - you wouldn't want to globally expose things that are only related to one functionality

#

In discord.py, this is a no-brainer because the whole functionality could be one cog with its shared state

#

I started thinking that listeners of the same name might even override each other, regardless that they have different paths. I wouldn't know the implementation but it's too suspicious

terse socket
terse socket
acoustic forge
terse socket
acoustic forge
#

Okay, this is cheap now. If anything, it is a skill issue to not distinguish same file names coming from unrelated components

terse socket
#

it aint hard to name a file playMessageCreate for audio and pointMessageCreate for level up system

acoustic forge
#

why even have directories if you still gonna hardcode the feature into the file name

terse socket
#

aside from the fact that those are horrible examples because audiobots are DOA and level up systems are a dime a dozen

acoustic forge
#

if you hardcode the category into all names, there isn't much to categorize

#

and this is not even hard to implement better, I actually created a custom class builder that encodes the folder structure into the name...

#

talking about skill issue

terse socket
#

categories arent hardcoded

#

also it should be noted that Sapphire supports fs-less pieces where piece names are defined through config only

acoustic forge
#

but to silently do the thing that you would basically never want...

terse socket
#

You're the only person among many to complain about this tiny detail so I wouldn't say never

acoustic forge
#

then perhaps it was about time

terse socket
#

🤷‍♂️ very unlikely to change plus if we would change it, it would be a breaking change (semver major)

acoustic forge
#

it's the first step to improvements

terse socket
#

but you're welcome to make an RFC issue 🤷‍♂️

acoustic forge
#

anyway, now that I caught this gotcha and have a fix

#

is there a way to keep the data of a certain feature more local than the container but less local than the same class?

terse socket
#

Basically the same as before but export the constant and import it elsewhere?

#

I have one other idea but I doubt you'll like it.

#

Extend sapphire's Piece class and add a property to it. Call it SpecificPiece for example. Then create SpecificAliasPiece, SpecificCommand and SpecificListener so you can access the custom property down the line.

#

But that's a LOT of structural overhead

acoustic forge
terse socket
#

Correct on the first part. We don't use cogging for skyra. We never had a use case for it.

#

Long ago only audio was split off before we removed it entirely but that was moreso to localise the code and not to localise something like you want

#

As for the second part, that's what any bot is really

#

Well any OO bot

acoustic forge
terse socket
#

That's basic OO with dependency injection ¯_(ツ)_/¯.

You'll also see it in other big frameworks like Spring Framework for Java and I think .net core does something like it too

#

Fwiw spring and .net are like the poster children of OO

acoustic forge
#

In Java, this dichotomy doesn't really exist. One file is one class. The static parts are plain singletons

terse socket
#

Have you used spring though. Raw java is not comparable at all.

acoustic forge
#

you would never import or export a variable

terse socket
#

Anyway Im driving now

acoustic forge
#

I haven't used Spring but I have used some ASP.NET Core and I'm pretty sure it's not like this. Everything is instantiated and passed around.