Understanding Flutter's SchedulerBinding in the context of an animated timeline - flutter

I'm trying to understand the part of this code below that uses SchedulerBinding.instance.scheduleFrameCallback(beginFrame);. beginFrame is listed in the other code block below.
The code comes from here, which is an animated timeline for Flutter. I don't expect anyone to read all this, obviously. But given some context, can you understand what for it is being used?
Context: this part of the code is inside a function called setViewport. The viewport of a timeline is simply the visible part of that timeline. So, once a viewport is set (a start and end point in the timeline are given), it ends animating something in the timeline. You can see that in the process of doing it, it calls SchedulerBinding.instance.scheduleFrameCallback, which is what I want to know what is used for. I obviously went into the page for SchedulerBinding but the explanation is so generic that I don't have an idea what it is used for.
if (!animate) {
_renderStart = start;
_renderEnd = end;
advance(0.0, false);
if (onNeedPaint != null) {
onNeedPaint();
}
} else if (!_isFrameScheduled) {
_isFrameScheduled = true;
_lastFrameTime = 0.0;
SchedulerBinding.instance.scheduleFrameCallback(beginFrame);
}
Here's beginFrame:
/// Make sure that all the visible assets are being rendered and advanced
/// according to the current state of the timeline.
void beginFrame(Duration timeStamp) {
_isFrameScheduled = false;
final double t =
timeStamp.inMicroseconds / Duration.microsecondsPerMillisecond / 1000.0;
if (_lastFrameTime == 0.0) {
_lastFrameTime = t;
_isFrameScheduled = true;
SchedulerBinding.instance.scheduleFrameCallback(beginFrame);
return;
}
double elapsed = t - _lastFrameTime;
_lastFrameTime = t;
if (!advance(elapsed, true) && !_isFrameScheduled) {
_isFrameScheduled = true;
SchedulerBinding.instance.scheduleFrameCallback(beginFrame);
}
if (onNeedPaint != null) {
onNeedPaint();
}
}

According to the project README, it's used to keep the Flare animations in sync:
"To have the animation reproduce correctly, it's also necessary to call advance(elapsed) on the current FlutterActor each frame. Moreover, the current ActorAnimation requires that the function apply(time) is called on it to display it's correct interpolated values.
This is all made possible by relying on Flutter's SchedulerBinding.scheduleFrameCallback()."

Related

How to trigger lazy loading function before user reaches scrollController.position.maxScrollExtent?

The result I want to achieve, is to trigger a lazy loading function before user reaches scrollController.position.maxScrollExtent, so in that way I will "minimize" the waiting time in the eyes of user.
In my case, I want the lazy loading function to trigger every time users scroll an 80% of the screen
Here is my working code snippet with lazy loading function triggered when user reaches the end of the screen (classic way):
scrollController.addListener(() {
if (scrollController.position.pixels == scrollController.position.maxScrollExtent) {
print('bottomReached');
// lazy load function
}
});
I tried to change the if statement to this:
scrollController.position.pixels >= scrollController.position.maxScrollExtent*0.8
but it didn't work as expected. What else can I do ? Thanks in advance.
You have to set a delta limit, which checks if the scroll is about to read max extent.
scrollController.addListener(() {
if (scrollController.position.pixels > scrollController.position.maxScrollExtent*0.8) {
print('bottomReaching');
// lazy load function
}
});
Here it checks if the current pixel is more than the delta value (maxExtent * 0.8) and if true, loads the function.
One more thing to note is that if in delta range, the function will be triggered with change of every pixel. So it is suggested to have a bool variable outside of scrollController.addListener to check if the function is already running.
For reference https://www.youtube.com/watch?v=EXJboDdIpEA
Happy coding!

Part of my PlayerExpChangeEvent is being overridden by vanilla

I'm making a spigot plugin (version 1.8.8) that has an function that I know works because it fires flawlessly through my command. However, when I call it at the end of a PlayerExpChangeEvent, it seems like vanilla leveling overrides the bar, making it go up way more that it is supposed to. Running the command/function after this happens makes the bar go back to how it is supposed to be. I've tried setting my event's priority to highest (and when that didn't work, to lowest) but no matter what my function appears to be completely ignored when called inside the event.
Here is some code:
#EventHandler(priority=EventPriority.HIGHEST)
public void onXpGain(PlayerExpChangeEvent event)
{
// Load custom levels from config
ArrayList<String> levelList = new ArrayList<String>(plugin.getConfig().getStringList("levels"));
if (!((String)levelList.get(0)).equals("none"))
{
Player player = event.getPlayer();
Iterator<String> var4 = levelList.iterator();
while (var4.hasNext())
{
String s = (String)var4.next();
String[] splits = s.split(" ");
int levelCompare = Integer.parseInt(splits[0]);
int playerLvl = player.getLevel();
// Detect if on correct tier, else continue iteration
if (playerLvl == levelCompare - 1)
{
// Calculate the player's new XP amount
int totalXp = player.getTotalExperience() + event.getAmount();
player.setTotalExperience(totalXp);
updateBar(event.getPlayer()); // <-- THIS IS THE FUNCTION
return;
}
}
// At max level
player.setTotalExperience(player.getTotalExperience() + event.getAmount());
player.setLevel(getHighestLevel(levelList));
player.setExp(1.0f);
}
}
And here is the function itself. Keep in mind that it works fine when called through a command and not an event. It's purpose is to use the player's total XP to set the level and bar. Neither set correctly in the event; it instead embraces vanilla leveling.
public static void updateBar(Player player) {
ArrayList<String> levelList = new ArrayList<String>(plugin.getConfig().getStringList("levels"));
int totalXp = player.getTotalExperience();
player.setLevel(getHighestLevelForXp(totalXp, levelList));
if (player.getLevel() < getHighestLevel(levelList)) {
int lvlDiff = getTotalXpToLevel(player.getLevel() + 1,levelList) - getTotalXpToLevel(player.getLevel(),levelList);
int xpDiff = totalXp - getTotalXpToLevel(player.getLevel(),levelList);
player.setExp((float)xpDiff/lvlDiff);
} else {
player.setLevel(getHighestLevel(levelList));
player.setExp(0.0f);
}
return;
}
The command where the function works correctly is a bare-bones call to the function and doesn't need a mention here. Does anyone know how to get my event to override vanilla xp gain? The update works through the command, just not natural xp gain. It is already confirmed that the event DOES fire, as the rest of the event changes the internal xp amount, but the visual effects are overridden by vanilla. Can anyone help? Thanks.
Only setting the Player's EXP won't be enough for your desired behaviour. The Vanilla behaviour will still complete, as you're not changing how the event will add EXP to the player.
Currently, your event is working like this:
And PlayerExpGainEvent isn't cancellable, so you cannot undo it's addition of EXP.
What you can do instead is to set the EXP the event will add to 0, therefore not changing the player's EXP after your interception.
event.setAmount(0); //Cancelling the EXP addition
I would recommend to set your event to a high priority, so that other events that depend on Experience gain won't trigger when you set the amount gained to 0.

GetAssetPreview always returns null for UnityEngine.UI.Image

I have an array of prefabs i want to show a preview of in my custom editor. This works for gameobjects with a mesh renderer, for example the basic quad. However when i try to use AssetPreview.GetAssetPreview(tilePrefab.gameObject); on gameobject with a UnityEngine.UI.Image and a canvas renderer it always returns null.
Below is the part of the code that draws the previews.
public class MapEditor : Editor
{
public override void OnInspectorGUI()
{
for (int prefabIndex = 0; prefabIndex < TileSet.TilePrefabs.Count; prefabIndex++)
DrawIconTexture(prefabIndex, columnCount);
}
private void DrawIconTexture(int prefabIndex, int columnCount)
{
TileBehaviour tilePrefab = TileSet.TilePrefabs[prefabIndex];
Texture iconTexture = AssetPreview.GetAssetPreview(tilePrefab.gameObject);
Rect iconRect = GetIconRect(prefabIndex, columnCount);
if (iconTexture != null)
GUI.DrawTexture(iconRect, iconTexture);
else if (AssetPreview.IsLoadingAssetPreview(tilePrefab.gameObject.GetInstanceID()))
Repaint();
}
}
I know GetAssetPreview loads assets async, that is solved by the repaint. I have also tried
while(iconTexture == null)
iconTexture = AssetPreview.GetAssetPreview(tilePrefab.gameObject);
But that never finishes.
I also tried to use the texture of the Image
if (iconTexture == null)
iconTexture = tilePrefab.GetComponent<Image>().sprite.texture;
But that does not work because the sprite is in an atlas and all of the atlas is shown.
Edit: misread the question. I actually tried to use IsLoadingAssetPreview and IsLoadingAssetPreviews for few hours, without success. I ended up using a sad little trick
if (_previews.All(pair => pair.Value != null)) return;
_previews = GeneratePreviews();
I put that in the Update() loop of my EditorWindow. It's pretty hacky, I'm going to ask Unity if the AssetPreview methods are actually working.
Old answer, irrelevant:
You are not using the while loop and GetAssetPreview correctly.
GetAssetPreview will launch an asynchronous loading of the preview. To know if the preview is fully loaded you need to call AssetPreview.IsLoadingAssetPreview.
A pretty simple and brutal way of doing it (it will block execution) is :
var preview = AssetPreview.GetAssetPreview(item);
while (AssetPreview.IsLoadingAssetPreview(item.GetInstanceID())) {
// Loading
}
As usual, careful with while loops. Note that there is also a AssetPreview.IsLoadingAssetPreviews method with no parameters.
you should just use "using UnityEditor"

A way to check a BufferedImage repaint()?

I'm working in Eclipse and I want to know if I can make an if statement that checks to see if the BufferedImage has been painted/drawn onto the frame. For some reason, it's not painting the correct image because clickable regions are appearing on that picture when they are not supposed to.
For example, when I click the region to go from 4>5 everything is good. When I click from 5 to go to 4 I end up at 6 because the 'regions' from 4 are appearing in 5 (The image should always be painted before the clickable regions are shown) before it's even being painted. I want to restrict this to check if the image has been painted onto the frame first.
I really don't want to use anything else besides what I have right now (so no new classes being implemented to do this task), I really just want a simple yet effective way to resolve this. Here is what I'm talking about:
...
MouseAdapter mouseHandler = new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
repaint();
if(n==0)
{
if(e.getX()>=459 && e.getX()<491 && e.getY()>=111 && e.getY()<133
{
n = 4;
}
return;
}
if(n==5)
{
if(...)
{
n = 4;
}
return();
}
if(n==6)
{
if(...)
{
n = 5;
}
if(...)
{
n = 0;
}
if(...)
{
n = 6;
}
return();
}
}
...
I think you might need to give a little more information. The problem might lie in how you repaint, not whether it was painted.
If you are running another thread as your main program, you might instead want to send the mouse events synchronously to that so that the main thread can process the mouse click and then repaint.
Another solution might be to override the repaint method and paint the buffered images there, but you may have done that.
Also, a little off topic, I noticed that you used for loops to determine if the mouse was clicked in a specific area.
You could shorten the code:
for(int i=459; i<491; i++){
if(e.getX()==i){
for(int j=111; j<133; j++){
if(e.getY()==j){
//action taken
}
}
}
}
To:
if(e.getX()>=459 && e.getX()<491 && e.getY()>=111 && e.getY()<133{
//action taken
}
This would take up less space in your code and less time checking every pixel.
Back to your question.
I dont know of a function to tell if a buffered image has been painted. The ploblem that you are having though might of might not be in the code provided. Posting the rest of your code would be beneficial.
Okay I found the solution, I forgot to come back to this question and let you know. The problem was that the mouse was double clicking for some reason. You could almost say it was 'recursive'. I decided to move the mouseListener from inside the paintComponent to outside of it, and surely enough that fixed it.

Touches on transparent PNGs

I have a PNG in a UIImageView with alpha around the edges (let's say a circle). When I tap it, I want it to register as a tap for the circle if I'm touching the opaque bit, but a tap for the view behind if I touch the transparent bit.
(BTW: On another forum, someone said PNGs automatically do this, and a transparent PNG should pass the click on to the view below, but I've tested it and it doesn't, at least not in my case.)
Is there a flag I just haven't flipped, or do I need to create some kind of formula: "if tapped { get location; calculate distance from centre; if < r { touched circle } else { pass it on } }"?
-k.
I don't believe that PNGs automatically do this, but can't find any references that definitively say one way or the other.
Your radius calculation is probably simpler, but you could also manually check the alpha value of the touched pixel in your image to determine whether to count it as a hit. This code is targetted at OS X 10.5+, but with some minor modifications it should run on iPhone: Getting the pixel data from a CGImage object. Here is some related discussion on retrieving data from a UIImage: Getting data from an UIImage.
I figured it out...the PNG, bounding box transparency issue and being able to click through to another image behind:
var hitTestPoint1:Boolean = false;
var myHitTest1:Boolean = false;
var objects:Array;
clip.addEventListener(MouseEvent.MOUSE_DOWN, doHitTest);
clip.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
clip.buttonMode = true;
clip.mouseEnabled = true;
clip.mouseChildren = true;
clip2.addEventListener(MouseEvent.MOUSE_DOWN, doHitTest);
clip2.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
clip2.buttonMode = true;
clip2.mouseEnabled = true;
clip2.mouseChildren = true;
clip.rotation = 60;
function doHitTest(event:MouseEvent):void
{
objects = stage.getObjectsUnderPoint(new Point(event.stageX, event.stageY));
trace("Which one: " + event.target.name);
trace("What's under point: " + objects);
for(var i:int=0; i
function stopDragging(event:MouseEvent):void
{
event.target.stopDrag();
}
function realHitTest(object:DisplayObject, point:Point):Boolean
{
/* If we're already dealing with a BitmapData object then we just use the hitTest
* method of that BitmapData.
*/
if(object is BitmapData)
{
return (object as BitmapData).hitTest(new Point(0,0), 0, object.globalToLocal(point));
}
else {
/* First we check if the hitTestPoint method returns false. If it does, that
* means that we definitely do not have a hit, so we return false. But if this
* returns true, we still don't know 100% that we have a hit because it might
* be a transparent part of the image.
*/
if(!object.hitTestPoint(point.x, point.y, true))
{
return false;
}
else {
/* So now we make a new BitmapData object and draw the pixels of our object
* in there. Then we use the hitTest method of that BitmapData object to
* really find out of we have a hit or not.
*/
var bmapData:BitmapData = new BitmapData(object.width, object.height, true, 0x00000000);
bmapData.draw(object, new Matrix());
var returnVal:Boolean = bmapData.hitTest(new Point(0,0), 0, object.globalToLocal(point));
bmapData.dispose();
return returnVal;
}
}
}