Loading multiple models and hiding nodes - autodesk-forge

I would like load two models and hide some nodes directly after the models are loaded.
I add event listeners to GEOMETRY_LOADED_EVENT and OBJECT_TREE_CREATED_EVENT to see when the loading is finished. Because the loading is done asynchroniously, either one of the models can be loaded last.
So I set the model I want to hide the nodes from active with. And after that hide the nodes I want to hide.
viewer.modelstructure.setModel(instanceTree);
This works some of the time, but it does not seem to work all the time. Is there some other event I should listen to in order to know the loading is finished? Or is there some way or event to make sure setModel(instanceTree) has finished?

Is your question about identifying the model which events are fired for ?
In the latest versions of the viewer API, viewer.loadModel takes a callback that returns the model instance being loaded, this should let you know the model before those 2 other events are fired:
Viewer3D.prototype.loadModel = function(url, options, onSuccessCallback, onErrorCallback, onWorkerStart) {
// ...
function onSuccess(model) {
self.model = model;
self.impl.addModel(self.model);
if (self.loadSpinner)
self.loadSpinner.style.display = "None";
if (self.model.is2d())
self.activateLayerState("Initial");
registerDimensionSpecificHotkeys();
if (onSuccessCallback) {
onSuccessCallback(self.model);
}
}
// ...
}
And in the event handlers the argument also contain the model instance for which the event is fired:
this.viewer.addEventListener(
Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, (args) => {
console.log(args)
})
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT, (args) => {
console.log(args)
})
Let me know if I'm missing something and if it doesn't work please provide some code that illustrates your workflow.
Thanks

Related

Is it okay to create clicklisteners (or other listeners) inside a viewmodelscope?

I have a a fragment containing a googleMap where I am creating a bunch of Markers (which also is clickable). They are spiced with different information (colors, shapes and so on) from a room livedata query. In addition I have some MaterialButton buttons (which are styled as pushbuttons) where I toggle the Marker visible status on. At the moment, the "setup" of theese markers takes some time (200ms-2 secs, depends of amount of markers). To get out of that waiting, I was planning to use a viewmodelscope. Since there are some clicklisteners for theese buttons defined in there (they should do some action with the markers), will they still be alive when the viewmodelscope coroutine section ends, and If they are alive, do they still live in the correct coroutine-context, and do I need to do some housekeeping on the listeners when fragment and/or viewmodel ends?
I.E:
class MapsFragment:Fragment(){
private lateinit var mapsViewModel : MapsViewModel
private lateinit var googleMap : GoogleMap
//...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mapsViewModel = ViewModelProvider(requireActivity()).get(MapsViewModel::class.java)
_binding = FragmentMapsBinding.inflate(inflater, container, false)
val root:View = binding.root
//...
return root
}//onCreateView
//...
override fun onViewCreated(view: View, savedInstanceState:Bundle?){
super.onViewCreated(view, savedInstanceState)
//...
mapFragment?.getMapAsync(_googleMap->
_googleMap?.let{safeGoogleMap->
googleMap = safeGoogleMap
}?:let{
Log.e(TAG,"googleMap is null!!")
return#getMapAsync
}
//...
mapsViewModel.apply{
liveDataMapsListFromFiltered?.observe(
viewLifecycleOwner
){mapDetailList->
viewModelScope.launch{
binding.apply{
//...
siteMarkers.map{
siteMarker.remove() //removes existing markes from map on update
}
siteMarkers.clear() //empty the siteMarker array on update
//...
mapDetailList?.map{
it.apply{
//...
coordinateSiteLongitude?.let { lng->
coordinateSiteLatitude?.let { lat->
siteMarkerLatLng = LatLng(lat,lng)
siteLatLngBoundsBuilder?.include(siteMarkerLatLng)
}
}
//...
siteMarkerLatLng?.let { safeSiteMarkerLatLng ->
val siteMarkerOptions =
MarkerOptions()
.position(safeSiteMarkerLatLng)
.anchor(0.5f, 0.5f)
.visible(siteMarkerState)
.flat(true)
.title(setTicketNumber(ticketNumber?.toDouble()))
.snippet(appointmentName)//TODO: Consider build siteId instead
.icon(siteIcon[iconType])
siteMarkers.add(
googleMap.addMarker(siteMarkerOptions) //Here are the markers added
)
}//siteMarkerLatLng?.let
}//it.apply
}//mapDetailList?.map
onSiteCheckedChangeListener?.let{
fragmentMapsMapTagSelector
?.apTagSelectorMaterialButtonSite
?.removeOnCheckedChangeListener(it) //clearing listener on button before update
}
onSiteCheckedChangeListener = MaterialButton.OnCheckedChangeListener { siteButton, isChecked ->
siteMarkers.map {
it.isVisible = isChecked
}
}.also {
fragmentMapsMapTagSelector
?.mapTagSelectorMaterialButtonSite
?.addOnCheckedChangeListener(it)
}
//Will this onCheckedChangeListener still survive when this viewmodelscope runs to the end ?
}//binding.apply
}//viewModelScope.launch
}//liveDataMapsListFromFiltered.observe
}//mapsviewModel.apply
}//getMapAsync
}//onViewCreated
}//MapsFragment
I think you misunderstand what a CoroutineScope is. It determines the lifecycle of coroutines that it runs, but not of the objects created in the process of running those coroutines.
viewModelScope is a CoroutineScope that automatically cancels any coroutines it is running when the associated ViewModel is torn down. The coroutine doesn't know what you're doing with it. Cancelling a coroutine merely stops it from running to completion, like returning from a function early. In your code, you set your listeners and haven't stored references to them besides in the views they are set to, so their lives are tied to their respective view's lives.
If you were going to use a coroutine in your fragment to set up something for your UI, you would use the Fragment's lifecycleScope, not the ViewModel's viewModelScope. Like if you were fetching something to show in your UI, you would want that coroutine to be cancelled when the Fragment is destroyed, not the ViewModel which might be outliving the Fragment.
Your use of a coroutine in your example code looks pointless, because I don't see any blocking or asynchronous suspend functions being called. You mentioned setting up site markers is taking like 200ms. I'm not familiar with Google Maps since I haven't used it in the past several years, so I'm not sure which part is time-consuming. Usually, UI elements do not allow you to interact with them on background threads, so you might be out of luck. But maybe the time-consuming part is allowed to be done on background threads. You'll have to read the documentation. Using a coroutine for this won't make it take less time, but can prevent the UI from stuttering/freezing.
If you were going to do some long computation with a coroutine, you would need to switch dispatchers to do the blocking work and interact with the UI elements back on the main dispatcher. Simply putting something in a coroutine doesn't make it take less time, but it provides a convenient way to do something on another thread and then continue on the main thread after the result is ready. For example:
lifecycleScope.launchWhenStarted { // lifecycle coroutines launch on main thread by default
val result = withContext(Dispatchers.Default) { // switch to dispatcher for background work
doTimeConsumingCalculation()
}
// back on main thread:
applyResultsToMyViews(result)
}
By using launchWhenStarted instead of launch, a Fragment's lifecycleScope will pause the coroutine when the Fragment is not attached, which will prevent potential crashes from trying to update UI using requireContext() or requireActivity() when there is no Activity.

Create delay in ActionScript 3 function

I have a function in Adobe Flex 4 (ActionScript 3) that accepts an object and returns an ArrayCollection...
If a certain global variable is set to true, I want the function to delay itself for 3 seconds before running. Otherwise I want the function to run as normal.
The problem is, if I use a Timer, that timer calls a separate function, and that function cannot return anything to my calling function, nor can the function it calls accept any parameters, so it's not like I can call my own function recursively after the TimerComplete event fires... And a recursive call wouldn't work anyway, because it would return the ArrayCollection to the timer-result function, not to the original calling function...
I need a delay within the function, not a delay that causes me to go outside that function. But I cannot figure out how to do it.
Something like this is what I need to do:
private function createArrayCollection(myObject:Object):ArrayCollection {
var myArrayCollection:ArrayCollection = new ArrayCollection();
if (globalWaitBoolean) {
//delay here for 3 seconds, somehow
}
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
}
So... Any ideas on how to accomplish this without calling an external Timer function that cannot return an object back to my original function?
Thanks,
The way you want it you will have your whole application to lag for 3 seconds, unresponsive to any user input and external events. But it is possible, sure:
import flash.utils.getTimer;
private function createArrayCollection(myObject:Object):ArrayCollection
{
var myArrayCollection:ArrayCollection = new ArrayCollection;
if (globalWaitBoolean)
{
var waitUntil:int = getTimer() + 3000;
// Method getTimer() returns time in ms passed since app start.
// So you just have to wait until it is greater than appointed time.
while (getTimer() < waitUntil)
{
// Do nothing.
}
}
return (myArrayCollection);
}
Still, if you want to do it in a correct way of doing it:
import flash.utils.setTimeout;
private function callerMethod():void
{
// Blah blah blah.
// ...
// Finally.
createArrayCollection(sourceData, asyncResult);
}
private function createArrayCollection(myObject:Object, handler:Function):void
{
var result:ArrayCollection = new ArrayCollection;
if (globalWaitBoolean) setTimeout(handler, 3000, result);
else handler(result);
}
private function asyncResult(source:ArrayCollection):void
{
// The rest of your processing code.
}
Normal (synchronous) code flow would not return until the value is prepared, so should you desire to actually wait for 3 seconds while not allowing your app to do anything, use getTimer() approach from #Organis's answer. If you'll go for an asynchronus result, you'll need to face and overcome some more problems.
First, when do you expect your returned ArrayCollection to actually arrive. Speaking of code design, asynchronous code requires a whole lot of assumptions, thread safety etc etc, and even while AS3/Flash does not have true multithreading unless you count Workers, the code flow with events is not as obvious. So, whoever called your createArrayCollection() MUST NOT expect value returned from it right away. So, speaking about your direct question, NO, you can't avoid timers of some sort if you desire a responsive application. But you can use them with an approach that would involve an indirectly returned result.
Second, whether there might be concurring requests for more array collections from objects if your app would require these - you have to prepare for any kind of interference that might be caused by this. Say your function is triggered by a button click - what if that button would get clicked more than once in 3 seconds?
Third, actual route to processing code is not direct with asynchronous return. You need either a callback, an event handler (which is essentially a semi-native callback), a code that periodically checks for value presence (enter frame handler, etc) or a similar trick to gather the value that's returned asynchronously, and then transfer it to any relevant code that would process it further. Therefore, you would need to design an interface capable of receiving complex data (source object forward, array collection backward) and then carefully test it against all the possible cases and flaws.
An example of implementing all that is very long, I'll try to outline it somehow. Ler's assume you have a sort of "server" class that accepts requests for data and processes it synchronously (no wait) or asynchronously (wait). It accepts a source object of type "T" and provides a newly created object of type ArrayCollection, supplied as a parameter to whatever callback function sent to it. Also it accepts a delay (a simple way to show sync/async return would be a boolean, but why not getting an int?) as a parameter, and guarantees (to the extent of event model limitations) that after this delay the callback will be called ASAP. The architecture will then look like this:
class Processor {
Dictionary requests; // here all the requests that are delayed will be stored
public function dpr(source:T,callback:Function,delay:int=0):void{...}
// creates requests and stores them
private function syncProcess(source:T):ArrayCollection {...}
// whatever routine you want to get variably delayed
private function processTimeout(e:Event=null):void {...}
// processes events from "setTimeout()" and calls callbacks
}
Note that asynchronous approach forced to create three more entities than a synchronous one. First is the request holding structure (the dictionary here), second is timeout event handler, third is whatever callback you'll desire to get called when the data is ready. The code flow would go like this:
Synchronous call would result in the callback directly called from within the class: request->processTimeout->syncProcess()->callback. Asynchronous call will have the callback called from within Timer::timerComplete event handler via setTimeout called within request, with data that originally came from request stored in requests.
You could use an embedded/inline function:
private function createArrayCollection(myObject:Object):ArrayCollection {
var myArrayCollection:ArrayCollection = new ArrayCollection();
if (globalWaitBoolean) {
var milliseconds:int = 3000;
//delay here for 3 seconds
setTimeout(function()
{
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
},
milliseconds);
}
else
{
//Here I do the stuff that uses the info in myObject to figure out what to
//put into the ArrayCollection I want to return
return (myArrayCollection);
}
}
The inner function will have access to all local vars of the outer function.

ObjectChanged Event not Bubbling for ValuesAdded/ValuesRemoved

I am trying to reduce the number of event listeners attached to my collaborative models. In order to do this, I have started listening to the ObjectChanged event instead of specific event types and delegating to other handlers. However it doesn't look like the ObjectChanged event is being bubbled properly for the ValuesAdded/ValuesRemoved changes on CollaborativeLists.
function onObjectChanged(e)
{
log('Changed: ', e);
}
// Placeholder, called when we load our doc through the realtime api.
function onDocLoaded(doc)
{
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
console.log('Drive document loaded: ', window.performance.now());
if (docRoot.has('testMap'))
{
docRoot.delete('testMap');
}
docRoot.set('testMap', docModel.createMap());
var testMap = docRoot.get('testMap');
console.assert(testMap, 'Test map required');
docRoot.addEventListener(gapi.drive.realtime.EventType.OBJECT_CHANGED, onObjectChanged);
var testList = docModel.createList();
testMap.set('testList', testList);
console.assert(testList, 'Test list required');
setTimeout(function ()
{
console.log('Begin Push');
testList.push('This is a test string');
console.log('End Push');
}, 1000);
}
The code above is run on doc load and demonstrates the problem. In this case, I would expect two ObjectChanged events to be fired (first for the list being set on the map and second for the string push into the list). The first event fires correctly, however the list push does not trigger an ObjectChanged event on either the 'docRoot' or the 'testMap'. As both of these are ancestors of the testList an event should be bubbled to them (based on https://developers.google.com/drive/realtime/handle-events#event_bubbling).
The ObjectChanged event however IS fired on the testList, so it looks like there is an issue with only the bubbling portion.
Is there a way of ensuring that the event bubbling will occur? Additionally, for events that are bubbling up, is there a way to stop bubbling partway?

Receiving FlexEvent.SHOW events in a ViewStack's grandchildren

The direct children of a ViewStack receive a FlexEvent.SHOW once the respective child view is shown. Unfortunately, the event information is lost at deeper child levels so that the grandchildren of the ViewStack do not receive the SHOW event.
Which approach would be advisable to tap into the SHOW events received by a ViewStack's direct children from a grandchild or grand-grand-child?
Using parent.parent.[...].addEventListener(FlexEvent.SHOW, [...]) would be possible, of course, but is kind of ugly as it will break as soon as the number of hierarchy levels between the two components changes.
Are there other events that are better suited?
The background of my question is that I would like to reload the grandchildren's content when it becomes visible again.
You could listen for the ADDED_TO_STAGE event in the grandchild view and find out if you're part of a view stack. If yes, then just listen to the view stack child's show/hide events.
... addedToStage="setMeUpForViewStack()" ...
Which is:
private var vsView:UIComponent = null;
private function setMeUpForViewStack():void
{
if (vsView) {
vsView.removeEventListener("show", vsViewShowHideHandler);
vsView.removeEventListener("hide", vsViewShowHideHandler);
vsView = null;
}
var obj:DisplayObject = this;
while (obj.parent != obj) {
if (obj.parent is ViewStack) {
vsView = obj;
break;
}
obj = obj.parent;
}
if (vsView) {
vsView.addEventListener("show", vsViewShowHideHandler, false, 0, true);
vsView.addEventListener("hide", vsViewShowHideHandler, false, 0, true);
}
}
And in your vsViewShowHideHandler you would reload the content (if the view is visible).
Basically this frees your from worrying about the level of nesting. It doesn't work with multiple nested view stacks though.
In the REMOVED_FROM_STAGE event handler you would forget vsView.
Although burying down into the viewstack would work I agree this is ugly and potentially can lead to headaches as soon as something changes.
A different approach could be to implement a global event dispatcher.
By having a static event dispatcher in a class, the grandchildren could subscribe to events from the static dispatcher from anwywhere within the application.
When the parent hears the FlexEvent.Show the handler could dispatch a custom event using the global dispatcher?

User vs. System events in Google Maps API v3

I've been reading the Google Maps API docs to see if it's possible to tell the difference between a system event vs. a user one?
For example, the zoom_changed event gets triggered when you use methods like setZoom, fitBounds, etc, which in my implementation is unsavoury, as I just want to know when the user actually changes the zoom level.
Unfortunately, the click event is only fired on the map itself, not the controls, so you can't rely on that method to help detect the users input.
Ideas?
Although I haven't been able to solve this using the Google Maps API, I have created a workaround which involves me calling this method before I change the map zoom or positioning without user interaction:
MapGraph.prototype.systemMove = function() {
var _this = this;
this.isMoving = true;
return setTimeout(function() {
return _this.isMoving = false;
}, 500);
};
And my event bindings look like this:
google.maps.event.addListener(this.map, 'dragend', function(event) {
if (!_this.isMoving) return _this.mapChanged();
});
Not perfect, but it does work.
Would love to see any other implementations though.
You may also consider an alternate solution I proposed in this Stack Overflow answer, which does not rely on mouse events to recognize user-initiated changes.
Instead of trying to recognize user events, add a flag to the map whenever a programmatic change is initiated with setZoom or fitBounds.
map.systemChange = true
map.setZoom()
Then check for (and reset) the flag in the event listener.
map.addListener('zoom_changed', function () {
if (map.systemChange) {
map.systemChange = false // Reset the flag for a system-initiated event
} else {
// Handle the user-initiated event
}
});