| 01:06 | <shu> | rbuckton: wrote up https://github.com/tc39/proposal-structs/blob/main/ATTACHING-BEHAVIOR.md, PTAL |
| 01:23 | <rbuckton> | A quick point regarding syntax, just as I mentioned before about wanting to avoid excess ceremony, I'm hoping we can go with something far shorter than shared struct class Foo {}. I imagine struct Foo {} and shared struct Foo {} would be sufficient to avoid ambiguity without needing the class keyword, and their behavior is different enough to justify the different syntax. |
| 01:27 | <rbuckton> | I'm also still not to keen on using class name as a global registry key, its far too easy to have collisions (so many things would be called
|
| 02:06 | <Ashley Claymore> | `shared struct com.bloomberg.ashleys.Node {}` |
| 02:11 | <Ashley Claymore> | shared structs don't have user code constructors (i now also see that the README.md is incorrect) |
| 02:23 | <shu> | Ashley Claymore: noted, good idea |
| 02:23 | <shu> | A quick point regarding syntax, just as I mentioned before about wanting to avoid excess ceremony, I'm hoping we can go with something far shorter than |
| 02:24 | <shu> |
|
| 02:25 | <shu> | that's not a dealbreaker, just a preference |
| 02:26 | <shu> | but do the broad strokes look good to you? |
| 02:37 | <shu> | i... suppose the @RegisteredStruct decorator could be implemented as applying during evaluation if it's some special built-in decorator that's not implementable by user code |
| 02:38 | <shu> | nothing in the decorator proposal precludes built-in native code decorators AFAIK |
| 02:49 | <rbuckton> | I've long believed there's room for built-in decorators with privileged capabilities that a runtime might be able to optimize ahead of time. For example, built in @enumerable(true|false), @writable(true|false), @configurable(true|false) decorators that can affect property descriptors since the Stage 3 proposal no longer has this capability. |
| 02:50 | <rbuckton> | Assuming they are trivially analyzable. |
| 02:53 | <rbuckton> | But an @RegisterStruct decorator has the opportunity to perform constructor replacement even without native code support, but I suppose in this case you're talking about it somehow patching the constructor to produce a this consistent with the registry during construction. |
| 03:29 | <Mathieu Hofman> |
|
| 03:31 | <Mathieu Hofman> | Ok I hadn't seen the registry freezing thing. It feels weird to not be able to create new registered shared structs, as that would completely nerf the point of the registry |
| 03:32 | <Mathieu Hofman> | That also makes a program potentially become invalid after freezing of the registry |
| 03:39 | <Mathieu Hofman> | Also because the registry is agent local, what would be the behavior in case of the same declaration in 2 realms, especially if one of those is a ShadowRealm? That "surprise" is more than that, it's a blatant violation of the callable boundary. |
| 03:43 | <Mathieu Hofman> | Regarding the agent-local fields, it feels weird to have static nonshared prototype automatically be created as an object instead of undefined like what a field would be. It also entices authors to go back to the pre-es5 way of populating the prototype, with assignment, which is a typical trigger of the override mistake. Which raised the question, what is the __ proto __ of that automatically created prototype object? If non-null, it's definitely going to cause override mistake issues in frozen intrinsics environments. |
| 04:08 | <shu> | That also makes a program potentially become invalid after freezing of the registry |
| 04:08 | <shu> | just like deleting capabilities defeat the point of those capabilities. isn't that the point of deniability? |
| 04:10 | <shu> | Also because the registry is agent local, what would be the behavior in case of the same declaration in 2 realms, especially if one of those is a ShadowRealm? That "surprise" is more than that, it's a blatant violation of the callable boundary. |
| 04:10 | <shu> | Regarding the agent-local fields, it feels weird to have |
| 04:11 | <Mathieu Hofman> | Except here that registry is syntactic. |
| 04:11 | <shu> | see ron's built-in decorator idea. i'm not wedded to syntax or even a programmatic API, though i have implementation reasons to prefer not programmatic, it is not instrumental |
| 04:11 | <Mathieu Hofman> | Decorators are still syntax |
| 04:12 | <shu> | delete O.p is still syntax... |
| 04:12 | <shu> | what line are you drawing? |
| 04:13 | <Mathieu Hofman> | But even if it was programmatic, that makes a program potentially become invalid. There is almost no existing capability exposing state built into the language today |
| 04:13 | <shu> | there is no precedent for this, agreed |
| 04:13 | <shu> | it is a new capability |
| 04:13 | <Mathieu Hofman> | I'm drawing the line at no new built-in exposing some global state |
| 04:14 | <shu> | this global state can be disabled for programs that don't use it |
| 04:14 | <shu> | if your program uses it and depends on the communication channel to get around a pretty bad DX issue, then... you opt into it |
| 04:14 | <Mathieu Hofman> | Or at least, it has to be entirely deniable, not just partially. |
| 04:15 | <shu> | in what sense is freezing the registry at program start not entirely deniable? |
| 04:15 | <shu> | in that case, you can't ever use registered shared structs, you must pass them around |
| 04:16 | <Mathieu Hofman> | Because creating a shared struct that would use that registry is disconnected, and is undeniable syntax. |
| 04:16 | <shu> | okay, then let's say the API is programmatic |
| 04:16 | <Mathieu Hofman> | It changes the behavior of other code |
| 04:16 | <shu> | and you can also delete the function that does the registering |
| 04:17 | <shu> | so deleting any existing function-based capability can change behavior of other code |
| 04:17 | <shu> | i don't see some bright line here |
| 04:20 | <shu> | even simpler, let's say the registry API is just its own global, which is configurable. registration is completely deniable |
| 04:21 | <shu> | for people who prefer a decorator-based approach, easy enough to write a class decorator that calls that API |
| 04:22 | <Mathieu Hofman> | It's late for me to articulate it, but I feel extremely uncomfortable with such a global state being added to the language, and the mitigation to deny that feature. Maybe if it was normative optional it'd be acceptable? |
| 04:22 | <shu> | sure, if normative optional makes you more comfortable |
| 04:22 | <shu> | we've done similarly for WeakRefs and finalizers |
| 04:22 | <shu> | yes let's pick this up tomorrow in the working session call |
| 15:51 | <rbuckton> | shu: I have some thoughts on the struct syntax, which I've posted here: https://gist.github.com/rbuckton/e1e8947da16f936edec1d269f00e2c53 |
| 15:52 | <rbuckton> | Given that static shared prototype; looks too much like a field definition, I opted to use with shared prototype; instead. |
| 15:58 | <rbuckton> | Also, given this back and forth on the registry, I still think the correlation based registry is still worth considering. Its more akin to Symbol.for(), since you cannot observe whether something is registered and it doesn't require API deniability. |
| 15:59 | <shu> | let's discuss the registry in depth at the working session call today |
| 16:00 | <shu> | which, PSA, is pushed back 30 minutes |
| 16:00 | <shu> | i had a last minute conflict, packed meeting today, sorry |
| 16:00 | <rbuckton> | ah, that's a problem. I have a hard stop at 2PM EDT/11AM PDT as I am hosting a meeting at that time. |
| 16:01 | <shu> | Also, given this back and forth on the registry, I still think the correlation based registry is still worth considering. Its more akin to |
| 16:01 | <shu> | ah, that's a problem. I have a hard stop at 2PM EDT/11AM PDT as I am hosting a meeting at that time. |
| 16:02 | <rbuckton> | If I have to asynchronously wait for an onmessage in the main thread before I can start sending data to the worker, that won't work for my use cases. |
| 16:03 | <rbuckton> | If the Worker has to do all the work before it sees the first message I post, that's fine. |
| 16:30 | <rbuckton> | Hmm. SharedArray only allows a max of 16382 ((2**14)-2) elements? |
| 18:31 | <lpardosixtosms> | Hmm. SharedArray only allows a max of 16382 ( |
| 20:19 | <shu> | rbuckton: thinking about your static declarative syntax idea |
| 20:21 | <shu> | what you can have is, like a layout declaration that is completely static and deduplicated, decoupled from shared struct declarations. shared struct declarations would then take a layout, and produce constructor functions in the executing Realm per-evaluation, much like classes, but since they are given a layout, they can be hooked up under the hood |
| 20:23 | <rbuckton> | can you provide an example of what this might look like, roughly? |
| 20:25 | <shu> | strawperson syntax:
|
| 20:25 | <shu> | there's no communication channel there AFAIK |
| 20:25 | <shu> | layouts will be transparently keyed by, like, source location |
| 20:25 | <shu> | or Parse Node in specese |
| 20:26 | <shu> | actually maybe Parse Node isn't sufficient, we might need a new concept |
| 20:26 | <shu> | since you reparse modules multiple times |
| 20:26 | <shu> | but that seems like a mechanical problem to describe... |
| 20:26 | <shu> | maybe a host hook |
| 20:26 | <rbuckton> | What happens if I do this:
|
| 20:27 | <shu> | easier for me to describe concretely in V8 implementation terms: you get 2 different constructor functions in your current Realm, both backed by the same map |
| 20:27 | <rbuckton> | Transparently keying by source location is fine as a fallback, but still doesn't work with bundlers. |
| 20:28 | <shu> | why not, they duplicate? |
| 20:28 | <shu> | why would a bundler make multiple copies of the same code |
| 20:28 | <rbuckton> | I fail to see how that resolves the correlation issue? |
| 20:28 | <shu> | think of map as the type |
| 20:29 | <rbuckton> | But how do I say that a SharedThingLayout in two threads are the same thing? |
| 20:29 | <shu> | that resolves the correlation issue because SharedThing1 instances have the same type in the engine as SharedThing2 instances |
| 20:29 | <rbuckton> | No, I think we're talking past each other |
| 20:30 | <shu> | oh, because i assume what you're doing is import { SharedThing } from 'structs.js', and 'structs.js' has the layout declaration |
| 20:30 | <shu> | so when multiple threads import it, they get the same deduplicated layout |
| 20:30 | <rbuckton> | forget thing1 and thing2. I'm talking about main thread SharedThingLayout and child thread SharedThingLayout |
| 20:30 | <rbuckton> | That's the problem. |
| 20:30 | <shu> | i understand, i'm saying there's one layout |
| 20:30 | <shu> | that layout is keyed off source location, in structs.js:NNN |
| 20:30 | <rbuckton> | oh, because i assume what you're doing is |
| 20:30 | <shu> | why is that a problem? |
| 20:31 | <rbuckton> | Main thread loads main.js, which is a bundle that includes layout SharedThing. Child thread loads worker.js which is a bundle that includes layout SharedThing in a different path and source location. |
| 20:31 | <shu> | so that comes back to my original question: do bundlers duplicate? |
| 20:32 | <shu> | you're telling me bundlers duplicate? |
| 20:32 | <rbuckton> | What deduplication? |
| 20:32 | <shu> | not _de_duplicate, duplicate |
| 20:32 | <rbuckton> | yes. |
| 20:32 | <shu> | main.js and worker.js in your example includes two, different inline copies of the layout source text |
| 20:32 | <shu> | is that right? |
| 20:33 | <rbuckton> | It depends on the bundler. Some bundlers and bundle configurations will just pack everything into a single file per entrypoint. Some bundlers/configurations will use shared entry points. |
| 20:33 | <shu> | like... just don't do that? |
| 20:33 | <shu> | bundlers can split out a 'layouts.js' |
| 20:33 | <shu> | because layouts will be specced to have this behavior |
| 20:33 | <shu> | if you duplicate it, that's not a semantics-preserving transformation |
| 20:33 | <shu> | so don't do that |
| 20:34 | <rbuckton> | Except for tree shaking |
| 20:34 | <shu> | tree shaking will be nonobvious in light of multithreading |
| 20:35 | <shu> | i do not see this as a problem that needs to be designed around |
| 20:35 | <rbuckton> | Tree shaking would mean the bundler can't elide any layout it sees, or any other code in the same file, lest the source positions change |
| 20:36 | <shu> | we're fundamentally talking about sharing across all worker threads |
| 20:36 | <shu> | sharing layouts requires a whole-world assumption |
| 20:37 | <shu> | you can't tree shake individual worker threads' code for shared layouts. the bundler instead needs to generate the set of shared layouts |
| 20:37 | <rbuckton> | You might as well define your layouts in a non-JS file, since you can't really put anything else with them for fear it can't be tree shaken to reduce bundle size. |
| 20:37 | <shu> | because the point is that they are ... shared |
| 20:37 | <shu> | i seriously doubt people want to express this out-of-band |
| 20:38 | <rbuckton> | you also have evaluation order issues. Unless we don't allow computed property names in layouts (i.e., no built-in symbols). |
| 20:38 | <shu> | there are no evaluation order issues because these are not evaluated, these are declarative |
| 20:38 | <shu> | so you are absolutely right, there are no computed property names |
| 20:39 | <rbuckton> | Do you need to define all instance fields in a layout? |
| 20:40 | <shu> | vs...? |
| 20:40 | <rbuckton> | If so, then you wouldn't be able to define symbol-named fields |
| 20:40 | <shu> | how do you not define all instance fields in a layout? these things are constructed sealed |
| 20:40 | <rbuckton> | I'm saying that if you must define them all ahead of time, and you can't use computed property names, you can't use symbols. |
| 20:40 | <rbuckton> | even for nonshared fields. |
| 20:41 | <shu> | and i'm saying that sounds good to me |
| 20:41 | <shu> | nonshared fields refer to field storage, not field names |
| 20:41 | <shu> | the field names are still shared |
| 20:41 | <shu> | strings are obviously shared |
| 20:41 | <rbuckton> | I disagree. |
| 20:41 | <shu> | i don't think symbols are so easy to use shared |
| 20:42 | <rbuckton> | Maybe not, but a lot of projects use user-defined symbols on classes currently, including NodeJS. That becomes another stumbling block to migrating to structs. |
| 20:42 | <shu> | how do you pass those user symbols around among threads? |
| 20:42 | <shu> | since symbols have identity |
| 20:43 | <rbuckton> | At the very least, you might be able to require they use symbols from Symbol.for() somehow so that they have the same identity, or you have to somehow correlate those as well somehow. |
| 20:43 | <shu> | there is literally 0 reason to use Symbol.for over strings |
| 20:43 | <shu> | they are just worse strings |
| 20:45 | <rbuckton> | Its very frustrating that threads can't just share the same code, like almost any other language with multithreading. |
| 20:45 | <shu> | the original sin is we made code have identity and first-class values |
| 20:45 | <shu> | hard to walk that back |
| 20:46 | <shu> | it's also very frustrating classes have identity and are first-class values |
| 20:46 | <shu> | i'm happy to try to carve out a space where some things don't have identity, like layouts |
| 20:46 | <Mathieu Hofman> | Its very frustrating that threads can't just share the same code, like almost any other language with multithreading. |
| 20:47 | <rbuckton> | I don't think that's [functions having identity] so much a problem. It's a problem for sharing, sure, but would that apply to a threading model where you don't have to spin up a whole new copy of your application code. |
| 20:47 | <Mathieu Hofman> | https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/xs/XS%20Marshalling.md#full-marshalling |
| 20:47 | <shu> | i think it is very much a problem |
| 20:47 | <shu> | everything having identity and being first-class values means by default they are not threadsafe |
| 20:48 | <rbuckton> | Sure, its not threadsafe. How is that bad? |
| 20:48 | <shu> | so... you can't just spin up a new thread without also loading a whole new copy of the world? |
| 20:49 | <rbuckton> | Why do you need a whole new copy of the world? |
| 20:49 | <shu> | i don't know what we're talkign about anymore, i was responding to your "it's frustrating" comment with my own reasons for why i find it frustrating |
| 20:49 | <shu> | we can drop this subthread, not a productive one |
| 20:51 | <shu> | back to the declarative layout idea at hand, yes, symbol-keyed names being precluded is a DX con |
| 20:52 | <rbuckton> | My point is more that, if we actually baked multithreading into the language, such that you don't have to spin up a copy of your application and could just use existing references, then we wouldn't have the correlation issue. We'd have other issues instead, but they are the pretty much the same issues as any other language with multithreading. |
| 20:53 | <Mathieu Hofman> | They are definitely the issues I don't want to see in JS |
| 20:54 | <shu> | My point is more that, if we actually baked multithreading into the language, such that you don't have to spin up a copy of your application and could just use existing references, then we wouldn't have the correlation issue. We'd have other issues instead, but they are the pretty much the same issues as any other language with multithreading. |
| 20:54 | <Mathieu Hofman> | I've spent some time in golang lately, and for a language that's supposed to make threads easier to deal with, I'm sorry but it was not |
| 20:54 | <rbuckton> | but as you say, back to the layout idea. Would this be so bad, though:
|
| 20:54 | <shu> | anyway, i can live with something like:
|
| 20:55 | <shu> |
|
| 20:55 | <rbuckton> | Sure. If that's the case, do we need the layout thing? |
| 20:55 | <shu> | why yes, because the 85% use case won't need computed property names |
| 20:55 | <rbuckton> | I think it adds far too much complexity. |
| 20:56 | <shu> | and the correlation API doesn't?? |
| 20:56 | <shu> | i am so confused |
| 20:56 | <shu> | this seems vastly simpler to use as a developer than manually coordinating |
| 20:57 | <rbuckton> | I'm talking about the idea I suggested in the meeting:
|
| 20:57 | <Mathieu Hofman> | if the layouts are shareable, is it unacceptable from a DX point of view to have factories for the shared struct, so that you create the shared struct after having received the layout ? |
| 20:58 | <shu> | rbuckton: i'm hung up on the second bullet-point without the introduction of a static, declarative concept like layout |
| 20:58 | <shu> | i don't know what "no struct reevaluation" means |
| 20:58 | <rbuckton> | the struct is the layout |
| 20:58 | <shu> | but the struct isn't a static thing |
| 20:58 | <shu> | it can include static initializers, etc |
| 20:58 | <shu> | and computed property names, as we've been debating |
| 20:59 | <rbuckton> | My suggestion was that we make struct a static thing, per-thread at least. |
| 20:59 | <shu> | it's like a static variable in C/C++ or something? the first evaluation caches it to something, subsequent uses never evaluate it again? that's... really weird, given closures? |
| 21:00 | <rbuckton> | Yes, something like that. Yes its weird. |
| 21:00 | <shu> | if the layouts are shareable, is it unacceptable from a DX point of view to have factories for the shared struct, so that you create the shared struct after having received the layout ? |
| 21:01 | <shu> | rbuckton: okay i guess it's possible, i just find those semantics really weird and less sensible than having a separate declarative, static concept |
| 21:01 | <shu> | what you're saying isn't static, it's cache-on-first-eval |
| 21:01 | <shu> | rather, singleton |
| 21:01 | <shu> | i'd prefer static semantics, you're saying singleton suffices |
| 21:02 | <shu> | why is singleton semantics needed if you deduplicate with an identity? |
| 21:02 | <rbuckton> | no qualms from me? I don't think this works. that's back to the thing1/thing2 issue. If I can write:
then I have two or more potential prototypes to contend with in a given thread. |
| 21:02 | <shu> | no you have one prototype |
| 21:02 | <shu> | L says "I have a per-thread [[Prototype]] slot" |
| 21:03 | <shu> | S1 and S2 refer to the same slot |
| 21:03 | <rbuckton> | But S1 and S2 could define conflicting methods with the same names. |
| 21:03 | <shu> | that sounds like they have different layouts! |
| 21:03 | <rbuckton> | No, that sounds like a very easy to run into user error. |
| 21:04 | <shu> | i really do not understand what you're getting at |
| 21:04 | <shu> | maybe layout was a poor choice of words here |
| 21:04 | <shu> | let's just call it nominal_shape to be unambiguous |
| 21:04 | <rbuckton> | I'm having a hard time understanding what it's intending to solve. |
| 21:05 | <shu> | the correlation problem! |
| 21:05 | <rbuckton> | It sounds like it solves the "v8 internal map" problem, not the correlation problem? |
| 21:06 | <shu> | S1 layout L and S2 layout L is intended to behave like, statically, Registry.register(L, S1) and Registry.register(L, S2), where L is the registry key |
| 21:06 | <rbuckton> | Ok. |
| 21:06 | <shu> | It sounds like it solves the "v8 internal map" problem, not the correlation problem? |
| 21:06 | <rbuckton> | So I do both of those in the same thread, what happens? |
| 21:08 | <shu> | assuming
|
| 21:08 | <shu> | the same semantics as if S1 and S2 were in different threads |
| 21:09 | <rbuckton> | What belongs to a shared struct S1 layout L {} then? the implementation? |
| 21:09 | <shu> | could you clarify what you mean by "belong"? |
| 21:10 | <rbuckton> | What is the point of shared struct in this model? What does it bring to the table aside from a constructor function? |
| 21:11 | <rbuckton> | In your first example, you showed initializers and methods. |
| 21:11 | <shu> | that's one part: shared struct declarations have evaluation semantics, and actually creates the constructor function, because those things are unshareable functions |
| 21:11 | <rbuckton> | Ok. |
| 21:11 | <shu> | the other part is, because it has evaluation semantics, it could have static initializers and method declarations that install those things onto the per-thread prototype |
| 21:12 | <rbuckton> | Now I do this:
What should I expect? |
| 21:12 | <shu> | if that's the textual order, "bar", as S2's evaluation will overwrite S1's |
| 21:12 | <rbuckton> | Does S2 overwrite foo? |
| 21:12 | <shu> | backing up, i think the missing context is i consider S1 layout L and S2 layout L to be code that you shuoldn't write |
| 21:13 | <shu> | the point of layout isn't to refactor common layouts (really poor choice of words) |
| 21:13 | <rbuckton> | Yes, you shouldn't write it, but you can write it. |
| 21:13 | <shu> | it's to separate static parts from runtime evaluation parts |
| 21:13 | <shu> | yes, and i'm explaining that it'll just have overwriting semantics |
| 21:13 | <rbuckton> | And if layout and shared struct must be tied together 1:1, there's no reason they should be separate. |
| 21:13 | <shu> | okay, i see our tastes differ substantially here |
| 21:14 | <shu> | your view seems to be, it is more important to syntactically bundle them, even if it means the semantics we get are singleton semantics instead of more static-y semantics |
| 21:14 | <shu> | my view is, it is less important to syntactically bundle them than to get static-y semantics |
| 21:15 | <shu> | why do it static-like if we don't go all the way? |
| 21:15 | <rbuckton> | I'm saying that, whatever restrictions we would have on the declaration of layout L {}, we could just have on shared struct S {} and not need the extra confusing separation of syntax. |
| 21:15 | <shu> | oh |
| 21:16 | <shu> |
? |
| 21:17 | <rbuckton> | Maybe that means shared struct isn't bundleable, and you need to stripe your bundle to ensure shared structs are always imported from the same place. |
| 21:17 | <shu> | that still requires some things you didn't like, like restriction around computed property names |
| 21:17 | <rbuckton> | Yes, that's precisely the syntax I proposed in https://gist.github.com/rbuckton/e1e8947da16f936edec1d269f00e2c53 |
| 21:19 | <rbuckton> | Why do computed property names have to be restricted? I'd like to be able to use [Symbol.iterator], among others, or I can't migrate to shared structs for some objects. And arguably, you'd want to be able to define [Symbol.dispose] as well. |
| 21:19 | <rbuckton> | Or [util.inspect.custom] |
| 21:19 | <shu> | if the layout portion of a shared struct declaration have static semantics instead of singleton semantics, how do you allow symbols? |
| 21:19 | <shu> | symbols do not exist at static time |
| 21:20 | <shu> | i have to go to a meeting but something is still very muddled for me here, i don't understand the semantics you have in mind |
| 21:20 | <shu> | i don't personally design things syntax first |
| 21:20 | <rbuckton> | IMO, if this solution doesn't allow for even the use of built-in symbols, it's not viable. |
| 21:21 | <rbuckton> | This isn't even all about syntax, its about what capabilities you are exposing or restricting. I would have the same concern if this was all API based with the same restrictions. |
| 21:21 | <shu> | okay, then i think the only viable thing we can both live with is singleton semantics, or a programmatic registry |
| 21:21 | <shu> | a static-first approach must have the computed property name restriction |
| 21:22 | <rbuckton> | If that's the case, so be it. I don't think I could support a mechanism that doesn't allow them. |
| 21:22 | <shu> | yeah that's fine |
| 21:23 | <shu> | i think we can make singleton semantics much less confusing by adopting the other restriction you raised during the call, like only allowing these at top-level |
| 21:24 | <shu> | can you elaborate on what you had in mind for the singleton semantics? is it keyed off source location? is it only singleton semantics if a with identity 'UUID' modifier is present? |
| 23:02 | <shu> | rbuckton: will you be in tokyo btw? |
| 23:24 | <shu> | wait a second, isn't there a pretty simple solution to the communication channel problem? if the key to the shared global registry is the combination of source location + with identity 'UUID' |
| 23:24 | <shu> | if it's the combination, you can't try to evaluate another definition to test for a collision |
| 23:25 | <shu> | it's specced to be a different key and will never collide |
| 23:25 | <shu> | Mathieu Hofman: am i missing something? ^ |