Hi Andrew, I just had a quick go at doing this locally. I *think* it should
be able to be done in MXMLRoyaleEmitter with minor changes.
I have not tested extensively, so I will just describe what I did and you
can see if it works, and if it is universally applicable.
The following is what I did:
change the
encodeWatcher method signature to this:
private void encodeWatcher(WatcherInfoBase watcherInfoBase, IDefinition
sourceDefinition)
and make 2 changes inside that method:
#1.
for the part that is:
writeNewline(ASEmitterTokens.DOUBLE_QUOTE.getToken() +
propertyWatcherInfo.getPropertyName() +
ASEmitterTokens.DOUBLE_QUOTE.getToken() +
ASEmitterTokens.COMMA.getToken());
replace with this:
String outputName = propertyWatcherInfo.getPropertyName();
if (sourceDefinition != null && sourceDefinition.isPrivate() &&
getMXMLWalker().getProject().getAllowPrivateNameConflicts()) {
JSRoyaleEmitter fjs = (JSRoyaleEmitter) ((IMXMLBlockWalker) getMXMLWalker())
.getASEmitter();
outputName =
fjs.formatPrivateName(sourceDefinition.getParent().getQualifiedName(),
outputName);
}
writeNewline(ASEmitterTokens.DOUBLE_QUOTE.getToken() +
outputName +
ASEmitterTokens.DOUBLE_QUOTE.getToken() +
ASEmitterTokens.COMMA.getToken());
#2
further down, comply with the new method signature:
replace:
encodeWatcher(ent.getValue());
with:
IDefinition childSourceDefinition = (IDefinition) ent.getKey();
encodeWatcher(ent.getValue(), childSourceDefinition);
And also in
outputBindingInfoAsData method:
comply with the new method signature for encodeWatcher
IDefinition sourceDefinition = (IDefinition) entry.getKey();
encodeWatcher(watcherInfoBase, sourceDefinition);
I think that might work for the general case, and maybe it can be
simplified. I only tested quickly and just looked at the output, did not
actually try the resulting app. If this does not work, it might give you a
lead.
-Greg
On Fri, Dec 7, 2018 at 7:58 AM Alex Harui <[email protected]> wrote:
> Hi Andrew,
>
> Thanks for looking into this.
>
> One of the things to keep in mind about the compiler that isn't
> immediately obvious is that the JS compiler can be thought of almost as a
> subclass of the SWF compiler. It isn't truly that way in the code, but the
> way it is now, even if you don't want SWF output, we actually run the most
> of the SWF compiler tasks on the source code before generating the JS
> because the transpiler's AST walk (that uses the Emitters to generate the
> JS) does not have the smarts for semantic analysis and similar things like
> binding and watcher analysis.
>
> The clue to that is which Eclipse projects or jars the various source
> files belong to. We will try not to introduce any files from the
> compiler-jx project/jar into the compiler and compiler-common
> project/jars. Doing so could create build order/dependency issues. And
> thus, we don't want to access the Emitters from the WatcherAnalyzer, nor
> have the WatcherAnalyzer know that it is talking to a RoyaleJSProject
> instead of a RoyaleProject (the latter which is designed for SWF output).
>
> And, even if you did find a way to access the Emitter, I think the same
> Watcher database is used for SWF output as well, so you don't always want
> to decorate the name of private variables in the data structure.
>
> After a quick think, I would suggest doing a bit more digging. I noticed
> that in the snippet you posted, a getter function appeared to be correctly
> generated. Is that part of the Watcher data or the Binding data? In the
> ActionScript code (DataBindingBase and subclasses) the Binding data is
> supposed to discriminate between "simple" property chains that it can
> create more efficient Bindings for vs GenericBinding that can handle more
> complex cases. The idea is that in some cases, we can't just access the
> value by an instance.propName lookup and a getterFunction is generated
> instead. This is absolutely required in SWF code since external classes
> like the Binding classes can't access private members of the class it is
> watching. It has always felt like a "cheat" to me that Binding pokes holes
> in the private access protection for Bindings, but that's pretty much the
> only way we can do it in SWF code (which also sort of means you lose the
> advantage of binding to private variables in the first place). In fact, I
> think one of the optimizations for Royale binding will be that bindings
> will be faster/smaller on public variables than private variables.
>
> So, having said all that, one possible solution is to have the
> ActionScript code use the getter function instead of the propertyName for
> private variables. I thought that the transpiler did not always output a
> getter function if public property chains could access the value. The AS
> code could then know to use a function instead of the property name. If
> some future version of JS actually does provide private access protection
> someday, we'll be glad we did this. Right now, the Royale JS binding code
> has been relying on direct access to private variables, which is also a
> "cheat", IMO.
>
> Another possible approach is to change what is stored in the Watcher
> database. Add new fields to store the fact that the property is private.
> Then in MXMLRoyaleEmitter, the watcher output code should have access to an
> emitter, and there is no need to access compiler-jx classes from the
> WatcherAnalyzer. This approach (just adding more info in the shared
> Analyzer code instead of generating code) is probably more
> platform/output-independent.
>
> Or maybe you'll need both.
>
> Hope that made sense. Good luck,
> -Alex
>
>
> On 12/6/18, 6:30 AM, "Frost, Andrew" <[email protected]> wrote:
>
> Hi all
>
> I found recently that binding had stopped working when we were using
> private variables, for example:
> <test:TestComponent id="testing" width="100" height="20"
> boundValue="{_model.value}">
> where we declare:
> private var _model : Model = Model.getModel();
>
> However it works with protected/public variables..
>
> The reason is clear when you look at the generated _bindings array,
> which contains:
> function() { return this.test_MainView__model.value; },
> and then
> "_model",
> "valueChange",
>
> So basically the watcher list is using "_model" whereas the property
> is actually generated as "test_MainView__model".
>
> Looking at the compiler code, there's a function on the emitter which
> does this translation:
> public String formatPrivateName(String className, String name) {
> return className.replace(".", "_") + "_" + name;
> }
>
> However I can't work out how to get the watcher analyser class to
> access the emitter.. I think this is where the logic should go, because
> within "WatcherAnalyzer. analyzeIdentifierNode" we're getting the name as a
> string and creating the watcher object based on this string. So I want to
> do:
> name = def.getBaseName();
> if (def.isPrivate() &&
> project.getAllowPrivateNameConflicts())
> {
> name =
> getEmitter().formatPrivateName(def.getParent().getQualifiedName(), name);
> }
>
> My issue is that there's no access that I can see to the Emitter.
>
> So I thought I'd ask:
>
> 1. does anyone know how I could actually get a valid reference to
> the emitter from this point in the code? i.e. just through the
> JSRoyaleProject object from what I can see..
> 2. or is there a better place where this should be? e.g. within
> getBaseName itself, or where the base name is set up on the definition..?
> 3. or could we move the "formatPrivateName" method to somewhere
> more accessible?
> 4. or can I just reproduce the "formatPrivateName" functionality
> here, and add comments so that future maintainers know to update two
> locations in case this changes?
>
> The last option is simplest but very hacky.. but would appreciate
> thoughts on the best approach.
>
>
> thanks
>
> Andrew
>
>
>
>
>
>