[B+C] Add MetadataProvider for on-demand metadata. Fixes BUKKIT-3625.#845
[B+C] Add MetadataProvider for on-demand metadata. Fixes BUKKIT-3625.#845crast wants to merge 1 commit intoBukkit:masterfrom
Conversation
|
I have a personal disdain for the Metadata system and how it encourages abstracting inter-plugin communication down to a String key versus using an actual class reference in the way Java was designed to leverage. I see the system akin to turning off all compiler warnings. It's simply a bad practice that is fraught with encouraging bad code. I suppose this commit has the right idea towards minimizing the performance and resource impact of using the Metadata system, but... meh. |
|
@EdGruberman While you make an excellent point, the main problem I see is in plugins where there's a class of plugins which interacts with another class of plugins, (not specifically one plugin with another specific plugin, where an API specifically into the other works swimmingly) My example from above, chat plugins, they often depend on something like a "user data" or "permissions" plugin. While it would be nice if there was a common interface between all of these, there isn't (excepting things like Vault, which adds more dependencies) and thus you're forcing everyone who wants to do this kind of communication (in realms less clear defined than permissions) to implement some sort of 3rd party API. Now, when the bukkit API takes into itself to standardize an interface (like permission checking) and provide ways for the permission plugins to hook into it, that is awesome, and it has worked swimmingly... for that specific use case. But when someone wants to go slightly outside that (like say, checking for a list of groups or other concepts outside basic permission checks) suddenly they're back to coding to individual plugins or using something a la Vault. The basic problem here is a question of dependencies, something that has plagued all sorts of package management systems. Heck, the java language itself has this problem and Java 8 has a major project as a part of it (google "java 8 jigsaw") to standardize on modules. The problem is how does a module declare "I provide feature X" in a non-strong-coupled way? The only way is if there's an interface available outside of the package that all the other plugins which provide feature X also implement (will that happen? no, probably not, but one can dream) and also a place to register them. And even if this ideal scenario were to happen, there would still need to be a place to register them, and metadata could be a way to do that. On the note of this devolving into a string key use, I've been trying to encourage standarization in users of metadata by having a spreadsheet of key exchange: http://goo.gl/HVRlX |
|
That spreadsheet does not remove the concern of String keys replacing interface definitions, it reinforces it. It epitomizes the point of my disdain. You are creating an external registry to compensate for plugins avoiding an external reference. That undermines the benefit of a dependency/reference that is managed internally with Java. The architectural concept of a shared library like Vault is good. It's a central registry of how plugins agree to interact utilizing standard Java mechanisms. I would propose it'd be even more ideal to have a plugin library that was essentially just common interfaces and let other plugins sign up as providers and implement accordingly. (Actually Vault may do this on some level at least.) It's essentially what the Metadata system and your spreadsheet are attempting to address, except it'd use standard Java mechanisms. |
|
While Vault works great, I'd say Vault is actually a terrible example of a good design in this case. Vault isn't a central registry of how plugins agree to interact, because Vault is the only provider of the three main interfaces, and all of the interop is done inside a huge number of modules in Vault: https://github.com/MilkBowl/Vault/tree/master/src/net/milkbowl/vault/permission/plugins This means that the author of Vault is providing a huge amount of shim code to make this inter-op work, and has to update his shims every time a permissions plugin changes their API. This does not encourage good design at all, as it can break in theory at any moment, and the onus is on one guy to fix it, not the authors of the various permissions plugins. Out of interest, I tried to find any other plugins which used the RegisteredServiceProvider paradigm and as far as public API is concerned, there's only other, and the same is true of both this and Vault: the plugin with the service provides the -only- implementations of the API. This has some bonuses of course, like you can add methods without breaking things. Now, there may be plugins which are split into multiple parts that are using RegisteredServiceProvider internally that I'm not aware of, but for this point, for plugin interop, why is it that there's only 2 plugins out there using this? (I have a number of possible explanations, but for brevity I will leave it out, suffice it to say there's a lot of limitations which prevent this from being a really viable way of plugin inter-op, some are systemic to java and some are systemic to the plugin developer base) |
|
My point is not how Vault does it specifically, the architecture of the centralized library plugin is what I'm encouraging... instead of some random spreadsheet people need to register with. That was my point about letting other plugins register implementations and using a centralized library/interface only plugin. If you did that instead of that spreadsheet, I'd be singing your praises. The reason a plugin hasn't done it to date is because either they get greedy and think they can do it better or they don't think they can get people to agree. If you are getting people to agree to String keys, what makes you think you can't get a centralized interface library working? |
P.S.: It's not that I don't think that having interfaces is better, but unless the interface is something implemented inside org.bukkit and distributed with the Bukkit API (like permissioning) it's bound to create so much headache that nobody will use it. This is systemic to Java and there's not much one can do about it. P.P.S: we should move this argument to IRC or forum private message |
|
For clarity in our discussion, Bukkit's RegisteredServiceProvider is just as bad of a concept as its Metadata system is. Neither are good substitutes for how Java is natively designed to manage class references. They both require an external reference in order for things to function as expected. In the RegisteredServiceProvider case it doesn't even eliminate the need for an external class reference, it only adds another layer to access it. In the Metadata system, if you stick to primitives or common Java objects you can eliminate the need for an external class reference, but you trade that advantage out for the need to agree externally to even Java itself to what you are asking for (a spreadsheet). In regards to your point about convenience (what you refer to as usage/scope locality): Convenience should not be the defining criteria for encouraging a solution that encourages bad code design. Proper dependency management is a fact of good architecture. If you have examples on how to reference your centralized library plugin to get at a provider, it's a simple copy/paste anyways. Assuming a junior developer will screw up a class import over agreeing to an external spreadsheet reference is an odd claim to make. They are equally problematic. A junior developer should be encouraged to learn how to properly manage their dependencies, not sheltered from the reality which will encourage further bad code. Your dependency concern falls under the same realm as your convenience concern. An example for copy/paste implementation will resolve this. Versioning (and your later point of concern over new interfaces) is clearly a concern and again not eliminated from the spreadsheet solution either. If someone wants a new way to handle data, they make a new String key. That's versioning through String keys. (e.g. chatPrefix gets replaced with advancedChatPrefix because it's "better".) The server admin concern over an additional download is a valid point, but also it has been proven to not be a concern with how Vault has been successful. You can't just dismiss it's success because you think it's a rarity. The point is it's possible if you market it and support it properly. You make the claim that if Bukkit doesn't implement it, it won't work. Yet your spreadsheet is exactly that. Bukkit does not implement the data creation that those String keys return. You are right about the forum discussion. If you want to continue further, let's hit the Bukkit forums and just tag me there. |
|
For continuance of the Metadata general discussion: http://forums.bukkit.org/threads/metadata-system-benefits.139846/ |
|
@crast (or anyone else) - If you are still interested in this, please address the following current concerns:
Thanks! |
|
@turt2live Is rebasing appropriate? I believe it will cause the comments on specific lines to be lost. If so, then I will proceed and make those changes. |
|
GitHub will supress them. So I would recommend rebasing as required and following up on the line comments before pushing to GitHub |
|
Rebased onto master. |
Metadata Providers are a new "dispatch" type system for allowing metadata to
be done truly on-demand, via calling into a contextual "provider" when metadata
is requested. Through use of generics providers can be registered for a large
range of bukkit types and they will be dispatched to the providers which respond
for those specific types.
* Created provider interface
* Added unit tests for provider usage
* Updated bukkit.Server interface with method for getting manager.
* Updated bukkit.Bukkit with static implementation
* Update generic type requirements of MetadataStore
* MetadataStoreBase now gets data from providers.
* Make OfflinePlayer Metadatable for consistency and generic guarantees.
|
Rebased again, squashed commits, updated PR description, cleaned up everything, much cleaner now. |
There was a problem hiding this comment.
Extra whitespace above this line
The Issue/Justification
At the very core of the metadata system is the idea of asking a question. A plugin asks a question, like “What is this player’s chat prefix?” and some other plugin is able to provide an answer.
The way it’s currently designed, for some plugin (say a chat plugin) to ask what a prefix is, some other plugin (say a permissions plugin or user manager) would have to set the "chatPrefix" key on every user before it's asked. (say when they log in.) This isn’t very effective, and it isn’t quite as “lazy” as one would hope, what if the value is never asked for? Then we've done all that work for nothing.
What would be useful, instead, if the metadata system provided a way for a plugin to register a metadata ‘provider’ for a key. Calling .getMetadata(key) would query the provider if no value is set, passing it the object, for an appropriate metadata value.
To use the chatPrefix example from before, MyAwesomePermissions would register a provider on a class target (say, Player or OfflinePlayer). Then at some point, BobsChatPlugin calls player.getMetadata("chatPrefix") which asks the provider for the chatPrefix. This also means if the chatPrefix is never requested (say you don't have a chat plugin to use it), there's no work done or memory used in creating the values.
For a full dissertation on issues with Metadata as a whole see: http://goo.gl/jRsX9
PR Breakdown:
Metadata Providers are a new "dispatch" type system for allowing metadata to
be done truly on-demand, via calling into a contextual "provider" when metadata
is requested. Through use of generics providers can be registered for a large
range of bukkit types and they will be dispatched to the providers which respond
for those specific types.
Using the interface would require the user to do something like
Testing Results and Materials:
Wrote extensive new unit tests that provides full coverage on the Metadata Provider interface/implementation, including writing some sample implementations and testing how providers receive data. This is in addition to some new full-stack unit tests in the craftbukkit companion to this ticket: Bukkit/CraftBukkit#1124
Relevant PR(s):
CB-1124 Bukkit/CraftBukkit#1124 - Implement MetadataProvider backend inside craftbukkit
JIRA Ticket:
BUKKIT-3625 - https://bukkit.atlassian.net/browse/BUKKIT-3625