-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Debug Node (New) #3058
Debug Node (New) #3058
Conversation
Hooray Jenkins reported success with all tests good! |
@vampcat: can you please explain the reasoning behind the various features of this PR versus the features I suggested in the last comment I made in #3051? In this context, you might want to use github comments to add notes to specific portions of the diff. Which is also an alternative to writing longer descriptions. |
"In this context I'm thinking that the WorldRendererImpl class could be a good candidate to become a system, perhaps doing initialization work in postBegin() and disposal in shutdown(). From that scenario we'd be able to have commands in the WorldRendererImpl class." "That been said, it would still be nice if somehow nodes can register with the renderer the commands they can accept. I.e. we could have a package-private WorldRenderer.registerCommand(uri nodeId, string commandName). Then, in the node, we'd have a handleCommand(string commandName, string arguments)." Other than the position of DebugCommands, I don't believe there is anything that you disapprove of in this PR? |
import org.terasology.rendering.dag.nodes.OutputToScreenNode; | ||
|
||
@RegisterSystem | ||
public class DebugCommands extends BaseComponentSystem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now, I'm hoping to use this as a "wrapper class" whose job is to pass on the commands to the responsible Nodes.
Note that the functions right now are hard coded, but by making a map of <String, Node> to store <Command, ResponsibleNode> we can easily allow arbitrary commands to be registered by FBOs (not entirely sure how that would work with @command , but I'm sure I can work something out). However, I'm hoping to do that later, since that does not give us any real benifits at this stage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the need for this wrapper class. I stand by the WorldRendererImpl class as implementing ComponentSystem and hosting a single command that passes through information from the console to individual nodes.
Note that the functions right now are hard coded, but by making a map of <String, Node> to store <Command, ResponsibleNode> we can easily allow arbitrary commands to be registered by FBOs (not entirely sure how that would work with @command , but I'm sure I can work something out).
Meanwhile, what I'm suggesting would be even simpler: in WorldRendererImpl you just store a <nodeUri,node> map. A command arrives in the form of a string "myNodeUri myCommand myArgs", you fetch the node through the map and pass the string "myCommand myArgs" to the node. You don't even have to handle the specific command for the node, just pass it through, so that registration is much simpler.
@RegisterSystem | ||
public class DebugCommands extends BaseComponentSystem { | ||
@Command(shortDescription = "Debugging command for DAG.", requiredPermission = PermissionManager.NO_PERMISSION) | ||
public void dagSetOutputFbo(@CommandParam("fboUri") final String fboUri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For instance, this function is a wrapped for OutputToScreenNode class, and directly passes all arguments to that Node.
return nodeInstance; | ||
} | ||
|
||
public void setFbo(String fboUri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are still handling the actual command inside the FBO, making every FBO responsible for it's own job.
vampcat wrote:
I think it has 2 advantages:
I'm not sure about what you say in regard to postBegin(). What I'm suggesting is to move the content of the whole constructor to postBegin(), leaving the constructor empty. Is this what you understood?
Actually, the renderer has a public dispose() method which does dispose a couple of things and that could become the shutdown() method, eliminating the need for a dispose() method and the special handling happening in StateIngame.dispose() method: the renderer would automatically shutdown together with all other systems as they are iterated over.
I'll comment on this replying to the github comments you added - thank you for that.
Not quite. As you implemented them right now every new command will need a new entry in the DebugCommands class AND a handler in the node. The way I suggested it you'd have to implement one command in WorldRendererImpl + command registration/deregistration functionality, one findNode() method in RenderGraph (which we probably need anyway for the API) and then registration/handling functionality in nodes. Not quite the same and not that difficult either: it would have taken you less time to implement all this than to discuss why you didn't implement it. If you had discussed your plan first, I would have spared you the effort. Architecturally speaking I asked for very specific things. You can try and convince me otherwise but it would have been best to do it where I described my vision before you went ahead with the implementation. I would have spared you some effort. |
@@ -59,4 +80,35 @@ public void process() { | |||
renderFullscreenQuad(); | |||
PerformanceMonitor.endActivity(); | |||
} | |||
|
|||
public static OutputToScreenNode getInstance() { | |||
return nodeInstance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely do not like this at this stage. There could be multiple Output nodes in a graph, with only one enabled at any given time. It might be meaningful at some point to impose such limitation, but I wouldn't do I just yet.
|
||
private StateChange bindFbo; | ||
|
||
public OutputToScreenNode(Context context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be better to rename PerformanceMonitor line 75:
PerformanceMonitor.startActivity("rendering/copyImageToScreen");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted, I'll take care of this in the next commit!
Hooray Jenkins reported success with all tests good! |
This was definitely not what I understood, thank you for clarifying this up. I believe I have implemented all the methods you asked for, except for the "command registration". I'm not entirely clear what the purpose of that command is, since I was able to get the system up and running without it. A little more detail about that function - or more specifically, why we need it - would be appreciated :D |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A small number of comments and requests for clarifications.
@@ -95,6 +97,19 @@ public void setEnabled(boolean enabled) { | |||
this.enabled = enabled; | |||
} | |||
|
|||
@Override | |||
public void setUri(String nodeUri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this package-private: apart from the RenderGraph, anything interacting with a node shouldn't be able to liberally reset the nodeUri. Let's also add a comment or a javadoc about it, to make it extra clear it's a conscious decision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original interface that this method is declared in (Node) is a public interface. Which means either I'll have to change it from public interface
to interface
, or we'll have to live with this.
Your call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't get it: you mean the whole interface has to become package-private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is what I meant.
At least, that's my understanding of how public interfaces in Java work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a look at it too, I assumed (wrongly) interfaces methods could use the same visibility keywords as class methods.
Ok, how about having a package-private BaseNode interface, including only the setUri
method and have the Node interface extend it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds.. hacky. Waaay too hacky.
Counter proposal : How about making the URI final, and setting it in the constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current thinking is that modules instantiate the nodes and then adds them to the graph via addNode. How can the graph be responsible for setting the uri (especially its namespace) if the node is already instantiated and the uri is final and set on construction?
@@ -28,11 +30,22 @@ public RenderGraph() { | |||
nodes = Lists.newArrayList(); | |||
} | |||
|
|||
public String addNode(Node node, String suggestedId) { | |||
public String addNode(Node node, String suggestedId) { // TODO: Change suggestedId to SimpleUri |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be done anyway. Why did you decide to leave it as a string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I left this as a string because there was a lot of grunt work, and I wanted to be sure I was going down the right path before I commited that much work on this.
( Weirdly enough, I'm fine with redoing entire systems, but not the grunt work :P )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh really? How peculiar! 😄
Anyway, I suggest we do it in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you did it already. Never mind then. 😊
nodes.add(node); | ||
node.setUri(suggestedId); | ||
return suggestedId; // TODO: for instance if "blur" present make id "blur1" and return it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's replace the TODO with:
// TODO: make sure URIs are actually unique: if "myModule:blur" is present the node gets the uri "myModule:blur2" instead.
// TODO: make sure the namespace in the uri is engine-assigned, so that only engine nodes can have the "engine:" namespace - everything else gets the namespace of the module.
fbo = staleGBuffer; | ||
break; | ||
default: | ||
// TODO: We should probably do some more error checking here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. How about a nullcheck? If it's null log a warning, back to the consolle ideally, and simply return.
// TODO: We should probably do some more error checking here. | ||
fbo = displayResolutionDependentFBOs.get(new SimpleUri(fboUri)); | ||
break; | ||
// TODO: Throw error or log something? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should simply print an error message back to the console. Failing that, a log warning will be enough for the time being. But the end game here will always be to inform the user back in the console given that's where the command started.
@@ -152,6 +159,21 @@ | |||
private DisplayResolutionDependentFBOs displayResolutionDependentFBOs; | |||
private ShadowMapResolutionDependentFBOs shadowMapResolutionDependentFBOs; | |||
|
|||
private static RenderGraph renderGraph = new RenderGraph(); // TODO: Try making this non-static | |||
|
|||
// Required for the reflection magic that Systems use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's interesting! How come? Aren't all the variables below null already?
EDIT: looking further down I have noticed the other constructor is still there rather then being in postBegin() where I expected it to move. What's going on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discovered this together earlier when @vampcat was trying to make WorldRendererImpl
a ComponentSystem - it failed to register entirely due to some init issue. I suspected it was from Gestalt wanting to use reflection, which goes wrong if there is no plain no-arg public constructor.
So the empty constructor essentially exists purely to let Gestalt register the system (via @RegisterSystem
). It isn't actually used normally anywhere in code. But if you don't initialize the class variables you get errors from Java thinking there's a way to instantiate the class without fully initializing.
So it is a kind of ugly workaround right now to let commands work in that class via way of making it a System. I dunno if that's better or worse than trying to make it register commands differently, or even using other classes.
Also not sure if it is / should be a best practice that ES classes be structured in a way where they are never explicitly created from somewhere else (since Gestalt's angle is to auto-register stuff via reflection magic and annotations)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @Cervator for the clarification. Can you please further clarify this paragraph:
Also not sure if it is / should be a best practice that ES classes be structured in a way where they are never explicitly created from somewhere else (since Gestalt's angle is to auto-register stuff via reflection magic and annotations)
I'm not sure what the "never explicitly created" part would look like in practice?
That been said,:
- I would still try to move the pre-existing constructor to postBegin(), so that there is only one constructor in place.
- I would also replace the
// Required for the reflection magic that Systems use.
comment with something more substantial, summarizing what Cervator wrote above in at least 2-3 lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding point 1: The WorldRendererImpl constructor takes in a context and a GLBufferPool. I can get the first one via registry (not a good idea, I guess?), but I really don't know how to go about the second one.
Regarding point 2: On it :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On point 1, please create a one-time-only method to pass context and buffer pool immediately after first instantiation of the renderer. Further invocations of the method should do nothing and log a warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On point 1, I'd say let's have one or two methods to set only once those variables, immediately after instantiation. Everything else can wait postBegin().
|
||
@Command(shortDescription = "Debugging command for DAG.", requiredPermission = PermissionManager.NO_PERMISSION) | ||
public void dagNodeCommand(@CommandParam("nodeUri") final String nodeUri, @CommandParam("command") final String command, @CommandParam("arg1") final String arg1) { | ||
// TODO: Convert arg1 to args[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this is not possible yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I was really confused by the existing javadocs of @Command
, and hence put this off for later.
I guess just like all of us, they also need some love :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it ok for you to do it now or do you want to leave it to a separate PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I already did it and pushed it in the last commit :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So... are you going to do it or leave it to another PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed you did it already. Never mind again.
Hooray Jenkins reported success with all tests good! |
1 similar comment
Hooray Jenkins reported success with all tests good! |
Sorry I couldn't review the changes today. I should be able to do the review tomorrow night, European time... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments. Getting there.
.useDepthBuffer().useNormalBuffer().useLightBuffer().useStencilBuffer(), fullScale); | ||
generateWithDimensions(new FBOConfig(FINAL_BUFFER, FULL_SCALE, FBO.Type.DEFAULT), fullScale); | ||
|
||
gBufferPair = new SwappableFBO(readOnlyGBuffer, writeOnlyGBuffer); | ||
gBufferPair = new SwappableFBO(gBuffer1, gBuffer2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these be named right away staleGBuffer and lastUpdatedGBuffer? Both in terms of variables and URIs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was discussed via slack and the new naming is correct, as at this stage there is no stale nor lastUpdated FBO: both haven't been touched yet.
@@ -30,13 +30,16 @@ public RenderGraph() { | |||
nodes = Lists.newArrayList(); | |||
} | |||
|
|||
public String addNode(Node node, String suggestedId) { // TODO: Change suggestedId to SimpleUri | |||
public SimpleUri addNode(Node node, SimpleUri suggestedId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... actually I'm thinking that in this case a string for suggestedId
is more appropriate: the namespace eventually will be assigned automatically and shouldn't be provided here - for the time being addNode
can add the engine:
namespace, until we begin work on the API proper.
But it's good that the method returns a SimpleUri.
node.handleCommand(command, arg1); | ||
public void dagNodeCommand(@CommandParam("nodeUri") final String nodeUri, @CommandParam("command") final String command, @CommandParam(value= "args") final String args[]) { | ||
Node node = renderGraph.findNode(new SimpleUri(nodeUri)); | ||
node.handleCommand(command, args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of things:
args
->arguments
- Would it be possible to use varargs instead of an array? I.e.
final String... arguments
?
And please @vampcat make sure to go through the comments on (allegedly) outdated commits. |
Uh oh, something went wrong with the build. Need to check on that |
I just had a look and removing the line placing the camera in the context only affects the FloatingTextRender.java class, a problem that can be easily solved by injecting the WorldRenderer instead and obtaining the camera from it. It might break modules, but they can easily do the same thing if they want the camera. How does that sound? |
It would in theory be an API violation which we're sort of in permissive mode for during alpha, to better understand when we might violate API. But on the other hand this is a new flavor - usually we just consider things marked with I hadn't thought about the scenario of a previously available thing in In this case you'd just make something else available where you can get the same thing, yet it could result in broken modules which would be bad. In this case I don't see any active users in my main workspace and it looks like Terasology/CombatSystem#32 uses the camera via the local player system instead. In the far future it might be nice to support some sort of redirect option for an object in |
@emanuele3d What do you suggest we do right now? Keep this out of Alpha 8's final stretch? |
I see value in being able to obtain the camera from only one place. @Cervator mentioned in his workspace he doesn't see modules using it (or obtaining via Context?) so this change should not have much of an impact and if it did it would be easy for a module to recover from it (grab the WorldRenderer from the context and get the camera from it). I certainly understand the philosophical issue @Cervator: where does the API begins and end? I think the commitment so far has been to not change @API-marked interfaces. It would be premature, I feel, to constrain what's in the Context/CoreRegistry though, given that it is slated for considerable long-term refactoring with the idea of switching to Contexts completely, including potentially subject-specific subcontexts (i.e. a RenderingContext) that better structure what's currently stored in the only Context. Perhaps we could have a separate versioning for the Contexts/CoreRegistry content, perhaps enforced/described by tests asserting the availability of specific class/instance pairs. I'm not sure if the separate versioning would help with the problem though. The tests are probably less controversial. I feel this is a discussion that should be had with Flo and perhaps also Martin and Immortius. Ultimately though, I think it is a mistake to have the active camera (which is instantiated by the WorldRenderer) stored in the Context. Once removed from the Context it is only one step away from it. So, to conclude, I'd prefer to go ahead as I recommended as I think it is a theoretically big but actually minor issue. |
|
||
break; | ||
default: | ||
throw new RuntimeException("Invalid command: " + command + "!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what happened here. This line originally had the following comment associated with it:
// TODO: Throw error or log something?
To which I replied (8 days ago):
It should simply print an error message back to the console. Failing that, a log warning will be enough for the time being. But the end game here will always be to inform the user back in the console given that's where the command started.
Why have you added exceptions then? Exceptions are for cases where a part of the code can't handle a problem and escalates it to the caller, potentially all the way to the user.
But here the code can handle the situation reasonably well: send a message back to the console that the command cannot successfully execute (with reasons) and do nothing. If that's too difficult for this PR, just send a warning to the log that the command couldn't execute, again with reasons, and return from the method doing nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I should have made this more explicit.
Console wraps all commands in a try-catch block, meaning any and every exception I throw will actually be intercepted by the console and will be printed as a console message. So far, I think it works pretty well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I should have made this more explicit.
Indeed. Thanks for clarifying this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given you haven't tackled this just yet, I thought adding a couple more notes...
fbo = displayResolutionDependentFBOs.get(new SimpleUri(arguments[0])); | ||
|
||
if (fbo == null) { | ||
throw new RuntimeException("Invalid fbo uri: " + arguments[0] + "!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's be a bit more specific here:
("No FBO is associated with URI '" + arguments[0] + "'")
|
||
break; | ||
default: | ||
throw new RuntimeException("Invalid command: " + command + "!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I should have made this more explicit.
Indeed. Thanks for clarifying this.
setFbo(fbo); | ||
|
||
break; | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's be more specific the line below. Let's write:
throw new RuntimeException("Unrecognized command: '" + command + "'");
And please notice the apostrophes between quotes...
Okay, I give up.
I traced the error back to a function NearestSortingList.java -> |
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
@Cervator: this PR took an usual amount of time for what it was, but it was worth it. We have set some good foundations. From my perspective it is ready for merge. |
Hooray Jenkins reported success with all tests good! |
Did some general testing locally - merged :-) Could confirm the command shows up when I As for the sorting thread stuff that's a known trouble spot that needs attention. See for instance #2742, #2774, #3006 |
Thank you for the merge @Cervator. I can confirm that right now the commands are just for me, @vampcat and anybody who wants to get deep into renderland - the typical user shouldn't have to worry about them for the time being. Eventually we might have commands that average users might want to use, i.e. to tweak some of the visuals. But this would be an intermediate step, awaiting for FlexibleConfigs both nodes and users can interact with and, eventually, awaiting for what we call the VDAG (Visual DAG) which would be a node-based GUI allowing the editing of the graph's topology AND the properties of individual nodes. |
Follow up to #3051
Extends the OutputToScreenNode (earlier CopyImageToScreenNode) to display arbitrary FBOs. Also adds the associated console command that can be used to control the fbo (
dagSetOutputFbo engine:fbo.ssao
).Side Note: The current method to handle the commands is pretty crude, but achieves the basic purpose - delegates the job of handling the command to the Node, and does not expose the internals of the rendering engine to unrelated classes.