#After setting class private field lambda still reads as null

1 messages · Page 1 of 1 (latest)

vestal basalt
#

This isn't the actual class since I think the problem might be easier to understand with less clutter. The actual class is Minecraft related, but enough print statements to make sure stuff is running has made me fairly confident its not a minecraft problem. I'll send them if it would be helpful, though.

public class Example {
  private Integer n = null;
  
  public void setN(Integer n) {
    System.out.println(n == null); // prints false
    this.n = n;
  }

  public void init() {
    Runnable runnable = () -> {
      System.out.println(this.n == null); // prints true
    };
    // I know that when the runnable is actually called setN() has been run
    doStuffWith(runnable);
  }```
steep skyBOT
# vestal basalt This isn't the actual class since I think the problem might be easier to underst...

Detected code, here are some useful tools:

Formatted code
public class Example {
  private Integer n = null ;
  public void setN(Integer n) {
    System.out.println(n == null );
    // prints false
    this .n = n;
  }
  public void init() {
    Runnable runnable = () -> {
      System.out.println(this .n == null );
      // prints true
    };
    // I know that when the runnable is actually called setN() has been run
    doStuffWith(runnable);
  }
#

<@&987246399047479336> please have a look, thanks.

steep skyBOT
#

While you are waiting for getting help, here are some tips to improve your experience:

Code is much easier to read if posted with syntax highlighting and proper formatting.

If nobody is calling back, that usually means that your question was not well asked and hence nobody feels confident enough answering. Try to use your time to elaborate, provide details, context, more code, examples and maybe some screenshots. With enough info, someone knows the answer for sure.

Don't forget to close your thread using the command </help-thread close:1027500463647621170> when your question has been answered, thanks.

sly shore
#

Looks like a common problem when accessing a variable in 2 seperate threads. The best thing to do would be either to make n volatile or use an AtomicInteger

vestal basalt
#

Is there a generic atomic for objects?

sly shore
#

Yes AtomicReference I think it's called

vestal basalt
#

Alright, thanks, I'll try that, close thread if it works.

vestal basalt
vestal basalt
sly shore
#

Yeah show the code

vestal basalt
#

Those should be the relevant ones

#

I can send more if helpful of course

#

The top one will be more useful I think

sly shore
#

tPos.orElse(null);

#

Can you check this line?

#

I'm on my phone btw, just an fyi

vestal basalt
#

Let me see if thats true though

#

Wait it is

#

Because of the line above

#

That prints successfully

#

Oh nvm I still need to test it

sly shore
#

So you got it yeah?

vestal basalt
#

Yeah I'm adding a print statement to check rq

#

Would be weird if thats the problem though since technically that code should be redundant in every meaning of the word.

#

@sly shore Nah, it still says that its null at that point, so setting it to tPos.orElse(null) shouldn't be causing the issue.

sly shore
#

is tPos not null?

vestal basalt
#

tPos is Optional.empty() somehow

sly shore
#

System.out.println(tPos.get());?

vestal basalt
#

In the second gist it passes in a ScreenHandlerContext.create() which should prevent it from returning Optional.empty()

vestal basalt
sly shore
#

I just want you to be sure that you're not actually setting it to null which is why the value is null

#

Empty would suggest there's no value

vestal basalt
#

I added the line System.out.println(pos.get() == null) before it’s set and it says true

sly shore
#

So the issue is actually

#

var tPos = screenHandlerContext.get((w, bp) -> bp);

#

Being null?

vestal basalt
#

Well but also that should be made redundant by setPos() which should override it

#

But that is an oddity that shouldn’t theoretically exist

sly shore
#

Where are you calling setPos I don't see a reference to it?

vestal basalt
#

The second gist

sly shore
#

EmonaTribesMain.logger.warn("RitualScreen Pos is empty");

#

Does this print out?

vestal basalt
#

Yup

#
true
56
[14:18:51] [Server thread/INFO] (Minecraft) [STDOUT]: THINGLSKNKLFGKSD
[14:18:51] [Server thread/INFO] (Minecraft) [STDERR]: CRAFTWITCH
[14:18:51] [Render thread/WARN] (Tribes of Emona) RitualScreen Pos is empty
true
1
[14:18:52] [Render thread/ERROR] (Tribes of Emona) POS is null! This is a bug!
true```
#

Full relevant logs

sly shore
#

RitualScreenHandlerFactory

vestal basalt
#

what about it?

sly shore
#

This receives the pos object

vestal basalt
#

Mhm

sly shore
#

So here:

#
public RitualScreenHandlerFactory(Text displayName, Inventory inv, World world, BlockPos pos) {
        this.displayName = displayName;
        this.inv = inv;
        this.world = world;
        this.pos = pos;
    }
#

Do a null check

#

What's the value you're passing for pos?

vestal basalt
#

A null check is done in createMenu

#
        if (pos != null) System.out.println("WAIT DO "); else System.out.println("Do I actually run?");
vestal basalt
#

and createMenu is where it is actually passed to the RitualScreenDescription

sly shore
#

Ah okay I see that now, sorry hard to follow on mobile lol

vestal basalt
#

Plus a print in setPos itself says its not null

vestal basalt
sly shore
#

Ah yeah okay so if it's not null it prints craftwich

#

Which is good, that's in your ouput

#

RitualScreenDescription constructor

#

You call init there

#

And within createMenu

#

The first init will be null? The second not null?

vestal basalt
#

I did because it was buggy when I didn't and I don't know why. It was never the intention to do so, more of a hotfix to try to figure out the bigger problem

#

Init has a bool flag to make sure its only run once

sly shore
#

So could it be running because of the constructor but then not running after you run setPos and call init again?

vestal basalt
#

Yes

#

The plan was originally to not have the init call in the constructor but it was too buggy

#

It wouldn't apply anything done in init()

sly shore
#

So if we run in the constructor, pos is null?

vestal basalt
#

Yes, but pos is mostly used in the lambda which by the time it is run (which I fully control) setPos has run

#

I interact with something ingame to run the lambda, effectively

vestal basalt
vestal basalt
sly shore
#

Can you also make initran into an AtomicBoolean

#

If it's used by multiple threads

vestal basalt
#

That should not be used by multiple threads, and even if it was, there are some unconditional prints that are only run once so it wouldn't make sense

#

Still doing it just incase

sly shore
#

Can you try it without init in the constructor? Do you at least get past if null check?

vestal basalt
#

So part of the problem is if I don't call init in the constructor init doesn't really apply, meaning the button doesn't appear so I can't run the lambda.

#

I don't know why thats the case but it apparently is

sly shore
#

Can you force the button to appear some other way or invoke the action on something else for the meantime?

vestal basalt
#

Not easily / in the same environment

#

AtomicBoolean has the same behavior still

sly shore
#

Fine can you then instead pass pos via the parameters?

vestal basalt
#

Already tried that

#
public RitualScreenDescription(int syncId, PlayerInventory pInv, ScreenHandlerContext context, BlockPos pos) {
        super(EmonaTribesMain.ritualScreenHandlerYucky, syncId, pInv, getBlockInventory(context, 1), getBlockPropertyDelegate(context));
        this.screenHandlerContext = context;
        this.pos.set(pos);
        init();
    }```
steep skyBOT
sly shore
#

I mean also add it to init()'s params

#

So init(pos)

vestal basalt
#

To init params? Intresting... Sure, I'll try that

#

But it would do nothing

#

All references to pos are either explicit this or in getClickSacrificeRunnable

vestal basalt
sly shore
#

But the very first lines of init

#
        initran = true;
        var tPos = screenHandlerContext.get((w, bp) -> bp);
        if (Objects.equals(tPos, Optional.empty())) EmonaTribesMain.logger.warn("RitualScreen Pos is empty");
        this.pos.set(tPos.orElse(null));```
#

Is this

vestal basalt
#

tPos is gotten from screenHandlerContext

#

not pos

sly shore
#

Sorry I meant add tPos as the param

#

And use that instead of the atomicref

vestal basalt
#

tPos isn't an atomicref

#

tPos is the result of screenHandlerContext.get((w, bp) -> bp)

#

For context here is ScreenHandlerContext source ```java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package net.minecraft.screen;

import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public interface ScreenHandlerContext {
ScreenHandlerContext EMPTY = new ScreenHandlerContext() {
public <T> Optional<T> get(BiFunction<World, BlockPos, T> getter) {
return Optional.empty();
}
};

static ScreenHandlerContext create(final World world, final BlockPos pos) {
    return new ScreenHandlerContext() {
        public <T> Optional<T> get(BiFunction<World, BlockPos, T> getter) {
            return Optional.of(getter.apply(world, pos));
        }
    };
}

<T> Optional<T> get(BiFunction<World, BlockPos, T> getter);

default <T> T get(BiFunction<World, BlockPos, T> getter, T defaultValue) {
    return this.get(getter).orElse(defaultValue);
}

default void run(BiConsumer<World, BlockPos> function) {
    this.get((world, pos) -> {
        function.accept(world, pos);
        return Optional.empty();
    });
}

}

sly shore
#

Yes but what I'm saying is pass the BlockPos object via the parameters and don't make it a global variable

#

So we can rule out it's not an issue with the multi threading stuff

vestal basalt
#

That would make multi threading problems worse, wouldn't it? if getClickSacrificeRunnable() got pos passed in before pos was set, it would stay null with no hope of changing

sly shore
#

Sec checking

#

That getClick method always expects the pos to not be null right?

#

And you're only gonna pass a not-null object?

vestal basalt
vestal basalt
sly shore
#

Okay are you using Intellij?

vestal basalt
#

Yup

#

You mean into setPos and stuff?

sly shore
#

I'll jump on my PC and teach you how to use the debugger if you're okay to screenshare

#

It's too fustrating trying to understand it while laying in bed lol

vestal basalt
sly shore
#

Yeah sure as long as we can set breakpoints and step through the code so we can see what values are actually being passed and when the functions are being called

vestal basalt
#

what

#

my code has been lying to me

#

how

sly shore
#

?

vestal basalt
#

You were right with one of your first guesses it seems, let me triple quadruple check

#

OR NOT

#

what is even happenign

#

pos isn't null

#

even though pos == null returns true

#

neither is tPos

#

in fact, tPos and pos are the same just as expected

#

I'm so confuzzled

sly shore
#

well I'm on PC now and the clarity is amazing

vestal basalt
#

I'm doing more testing since this makes -1 sense

sly shore
#

Well here's a breakdown of your code:

#
  1. createMenu is called
  2. "WAIT DO" is printed because pos is not null
  3. RitualScreenDescription is constructed and this takes us into the constructor logic
  4. init() is called
  5. var tPos = screenHandlerContext.get((w, bp) -> bp);
  6. "RitualScreen Pos is empty" is printed because tPos is null/empty
  7. this.pos.set(tPos.orElse(null)); sets pos to null because of the above

** at this point initran is true

  1. screen.setPos(pos);
  2. screen.init(); this method just returns because initran is true. So we can never reach the next lines of code.
vestal basalt
#

Wait this suddenly makes infinitly less sense

#

unless

sly shore
#

your original output:

#
[14:18:51] [Server thread/INFO] (Minecraft) [STDOUT]: WAIT DO 
true
56
[14:18:51] [Server thread/INFO] (Minecraft) [STDOUT]: THINGLSKNKLFGKSD
[14:18:51] [Server thread/INFO] (Minecraft) [STDERR]: CRAFTWITCH
[14:18:51] [Render thread/WARN] (Tribes of Emona) RitualScreen Pos is empty
true
1
[14:18:52] [Render thread/ERROR] (Tribes of Emona) POS is null! This is a bug!
true
#

this all happens and step 9 in the above steps I gave never happened

vestal basalt
#

Fascinating

#

init is ran twice

sly shore
#

I know

vestal basalt
#

Once with this.pos correct and once with it null

sly shore
#
    RitualScreenDescription screen = new RitualScreenDescription(syncId, pInv, ScreenHandlerContext.create(world, pos));
        screen.setPos(pos);
        screen.init();
#

that block of code calls init twice as I said the first time

vestal basalt
#

ScreenHandlerContext.empty() on the second one too

#

oh

#

I may be a dum dum and hate mojang in a hot second. Give me a minute

#

Well thats really dumnb

#

So I know the problem now

#

Have absouletly 0 clue how to fix it

#

Basically the server knows what pos is, the client thinks pos is null. The client handles gui stuff, and as such, the button press

sly shore
#

so the reason you're having this is because if you don't call init in your constructor, your button doesn't render correct?

vestal basalt
sly shore
#

try this change then:

#

Change the RitualScreenDescription constructor

#

to:

#
public RitualScreenDescription(int syncId, PlayerInventory pInv, ScreenHandlerContext context, BlockPos pos) {
    super(EmonaTribesMain.ritualScreenHandlerYucky, syncId, pInv, getBlockInventory(context, 1), getBlockPropertyDelegate(context));
    this.screenHandlerContext = context;
    setPos(pos);
    init();
}
#

and change createMenu

#
    public ScreenHandler createMenu(int syncId, PlayerInventory pInv, PlayerEntity player) {
        if (pos != null) System.out.println("WAIT DO "); else System.out.println("Do I actually run?");
        return new RitualScreenDescription(syncId, pInv, ScreenHandlerContext.create(world, pos));
    }```
vestal basalt
#

No, createMenu is ran on the server I bet I can check. But here is another file that has become relevant

#
package net.numra.emonatribes;

import net.fabricmc.api.ClientModInitializer;
import net.minecraft.client.gui.screen.ingame.HandledScreens;
import net.numra.emonatribes.tribes.RitualScreen;
import net.numra.emonatribes.tribes.RitualScreenDescription;

public class EmonaTribesClient implements ClientModInitializer {
    @Override
    public void onInitializeClient() {
        HandledScreens.<RitualScreenDescription, RitualScreen>register(EmonaTribesMain.ritualScreenHandlerYucky, (gui, inv, title) -> new RitualScreen(gui, inv.player, title));
    }
}
sly shore
#

ah right - so these 2 files are on different applications essentially

#

no that makes no sense

vestal basalt
#

Yeah. I'm betting the client is running this for the gui, the server is running creeateMenu

sly shore
#

why won't my server change fix it though?

#

you'll run the init once this way

#

and you won't have your null issue or the render issue

#

at least from the sounds of it anyway

#

unless you're implying the client is doing the render differently to how you expected it

vestal basalt
#

So whats happening is the client just knows I want a RitualScreen<RitualScreenDescription>. The server knows I want a RitualScreenHandler.createMenu

#

They use different constructors as a result, and so the client never sets pos because the client doesn't have the foggiest idea of what pos could be.

#

The server sets pos because RitualScreenHandler.createMenu knows what pos is and how to set it

sly shore
#

ah yeah you said it was empty each time

vestal basalt
#

Server knows the variables, Client doesn't. Client needs to know the variables, Server doesn't

#

Well technically best case the button press would be ran on server in all honesty

sly shore
#

so pass the values from the server to the client?

vestal basalt
#

But... I don't want to spend several years of my life making custom packet transfer in minecraft

#

This has become a fabric problem though, so I'll ask on their discord.

sly shore
#

did you try my above fix?

vestal basalt
#

Thanks for all your help

sly shore
#

give that ago before you move over there

vestal basalt
#

alright