Jump to content

  • Log In with Google      Sign In   
  • Create Account

Nypyren

Member Since 19 Aug 2002
Online Last Active Today, 11:29 AM

#5288855 Network Library that supporst RPCs

Posted by Nypyren on 26 April 2016 - 06:57 PM

The only "magic" part of UNET is that they replace the body of the sender's RPC method with a replacement that performs parameter serialization and sends the call, and generate the receiver-side deserialization code to call the function for you.

To do RPC manually, you just need to:

- Serialize parameters and function context (if applicable, like the gameObject's unique ID or whatever)
- Send the serialized information over the network like any other network data
- On the receiving end, deserialize the parameters and then call the appropriate function in the appropriate context (if applicable) using lookup tables (or reflection - it's debatable whether this is OK or not in the context of RPC - you maintain less code with reflection anyway).

If you want to handle returning a value back to the caller, you need an additional ID for the call itself, and a lookup table of code to call when you get a response. Since Unity doesn't have C# 5.0 await support yet, I would use a lambda-style continuation.


#5288531 Noob pointer question

Posted by Nypyren on 24 April 2016 - 11:06 PM

Based on 1st case, are 'a' and 'pA' themselves go to a stack memory for storing values after deletion? I mean, after deleting the memory they are pointing to, they themselves are still there... pointing to zero or whatever value isn't it?


Yes, a variable's value is stored somewhere as well (for a pointer, that value is the integer representing the memory address).

Variable storage depends on how it's declared:

Local variables and function parameters: Stored on the stack and/or in registers. The compiler decides based on how you use the variable and what optimizations it can do.

struct/class member fields: Stored as part of the struct/class (i.e. on the stack if it's a local variable or on the heap if you 'new' it).

Global/static variables: Stored in a non-stack, non-heap region of memory that is cleaned up when the program exits. You aren't allowed to 'delete' memory in this region.

Thread static variables: Stored in thread-local-storage. You aren't allowed to 'delete' memory in this region, either.


#5288479 Noob pointer question

Posted by Nypyren on 24 April 2016 - 12:54 PM

The short answer is: Only 'delete' what you 'new'.

The longer answer is:

You have to think about pointers separately from the memory they point to.

'delete' does not delete the pointer, it's deleting the memory the pointer points to. This also means that even if you have two pointers pointing to the same memory, you're only allowed to delete once.

Your second case allocates 'a' on the stack. pA points to that stack memory. You never need to 'delete' something on the stack, because stack memory gets cleaned up automatically when the function returns.


#5288080 How to deal with this unexpected counter-offer?

Posted by Nypyren on 21 April 2016 - 10:25 PM

Based on the reaction from the directors and president, I think you should also discuss things (everything!) with them a lot more. Be honest about what you like and don't like, discuss the things that you're interested in and what would make you happiest, and be tactful and open minded.

I think this will give everyone more information that helps guide your decision making process better.


Are there any big showstoppers you can focus on? Are you 'crumbling from a thousand tiny stress fractures'? Solving even the small nagging nonsense can improve job happiness a lot.

Personally I would stay where you are unless you just can't stand it.


#5287815 Object reference not set to an instance of an object

Posted by Nypyren on 20 April 2016 - 01:19 PM

Planet[i] = new PlanetDetails();
You can only skip that step if you're using an array of value types (int, bool, float, enum, struct - basically everything that's not a class).

When you make an array, its entries are all set to the "default" value of the corresponding type.

int: 0
float: 0.0f
double: 0.0
all the other numeric and enum types = whatever their concept of 0 is.
bool: false
class: null
struct: non-null, each individual field = its own default

In this case you have a class, so the default is null; You need to individually 'new' each element in the array.


#5287483 "Timer undeclared identifier" Is my compiler drunk? o.o

Posted by Nypyren on 18 April 2016 - 10:53 AM

Perhaps a mutual #include? Try this:
 
//#include "Timer.h"
class Timer;
Move the #include to your CPP file (or your PCH, if applicable).


#5287112 c++ should nested switch statements be avoided?

Posted by Nypyren on 15 April 2016 - 04:21 PM

(sword example)


I wouldn't make derived classes just to change values. I would have a data table of different kinds of swords, without any separate classes.

 
Of course not. I was merely giving an object-oriented example using C++. Were I to give a more practical demonstration, it would use an unnecessary amount of screen space for what was being conveyed.


I understand; although a newcomer won't! We have to be careful with what our demonstrations and examples teach to new programmers. In most of the derived class examples I see that are designed to show an idea to new programmers, it's REALLY often a case where an experienced programmer would say "Nooooo~, an experienced programmer would never do this!". The problem is that the newcomer will see the example and believe that it should be done that way. They don't yet know what good code vs. bad code looks like yet, and they don't know what all of the available options are for solving different problems.

I think that examples are ideal if they can be made so they are both understandable by new programmers and something that an experienced programmer would not object to. We can improve the entire programming community if we're careful with our examples to avoid misunderstanding.


#5287056 c++ should nested switch statements be avoided?

Posted by Nypyren on 15 April 2016 - 10:51 AM

(sword example)


I wouldn't make derived classes just to change values. I would have a data table of different kinds of swords, without any separate classes.


#5286952 c++ should nested switch statements be avoided?

Posted by Nypyren on 14 April 2016 - 06:54 PM

Here's what I do for my massively nested switch statements in my Intel x86-64 disassembler (which has five or six nested levels of switch statements)

1. Each nested switch goes in a separate function. A case statement in the first switch calls a function with another switch in it (if necessary - not all instructions nest all the way down).


2. The Intel instructions have this really tedious-to-enter prefix pattern for the SSE/AVX instructions:

The instruction's 66, F3, and F2 prefix(es) can be combined to change the instruction mnemonic entirely (in addition to the typical three opcode bytes).

Due to how the Intel manual's opcode map is laid out (the 66/F3/F2 prefixes are arranged in rows), in the 0F38xx and 0F3Axx switch tables, I do this (this is technically C# but the syntax is nearly identical):

Instead of switch once on the opcodeByte3 and then switch again on the SimdType, making one big 256-entry switch followed by 256x 4-entry switch functions (which would have no longer lined up nicely with the Intel manual), I rearrange the bits. I still switch twice, but the adjusted arrangement is more readable.

 
opcodeByte3 = reader.ReadByte();
            
// Encode the switch to make cases line up more exactly with the manual.
int row = opcodeByte3 & 0xF0;

switch (simdType)
{
    case SimdType.None: break;
    case SimdType.S_66: row += 1; break;
    case SimdType.S_F3: row += 2; break;
    case SimdType.S_F2: row += 3; break;
    case SimdType.F266: row += 4; break;
}

// This switch ignores the low 3 bits in order to map directly to rows.
// Case statements are be the leftmost column of each page.
switch (row)
{
    case 0x00: return FindOpcode0F38_Row0_None();
    case 0x01: return FindOpcode0F38_Row0_66();
    case 0x10: return FindOpcode0F38_Row1_None();
    case 0x11: return FindOpcode0F38_Row1_66();
    case 0x21: return FindOpcode0F38_Row2_66();
    case 0x31: return FindOpcode0F38_Row3_66();
    case 0x41: return FindOpcode0F38_Row4_66();
    case 0x51: return FindOpcode0F38_Row5_66();
                
    case 0x71: return FindOpcode0F38_Row7_66();
    case 0x81: return FindOpcode0F38_Row8_66();
    case 0x91: return FindOpcode0F38_Row9_66();
    case 0xA1: return FindOpcode0F38_RowA_66();
    case 0xB1: return FindOpcode0F38_RowB_66();
                
    case 0xD1: return FindOpcode0F38_RowD_66();
                
    case 0xF0: return FindOpcode0F38_RowF_None();
    case 0xF1: return FindOpcode0F38_RowF_66();
    case 0xF2: return FindOpcode0F38_RowF_F3();
    case 0xF3: return FindOpcode0F38_RowF_F2();
    case 0xF4: return FindOpcode0F38_RowF_66F2();
}
Each individual row function looks like this:
 
private bool FindOpcode0F38_Row0_None()
{
    switch (opcodeByte3)
    {
        case 0x00: return Valid(Opcode.PSHUFB, Pq, Qq);
        case 0x01: return Valid(Opcode.PHADDW, Pq, Qq);
        case 0x02: return Valid(Opcode.PHADDD, Pq, Qq);
        case 0x03: return Valid(Opcode.PHADDSW, Pq, Qq);
        case 0x04: return Valid(Opcode.PMADDUBSW, Pq, Qq);
        case 0x05: return Valid(Opcode.PHSUBW, Pq, Qq);
        case 0x06: return Valid(Opcode.PHSUBD, Pq, Qq);
        case 0x07: return Valid(Opcode.PHSUBSW, Pq, Qq);

        case 0x08: return ValidSimd(Opcode.PSIGNB, Pq, Qq);
        case 0x09: return ValidSimd(Opcode.PSIGNW, Pq, Qq);
        case 0x0A: return ValidSimd(Opcode.PSIGND, Pq, Qq);
        case 0x0B: return ValidSimd(Opcode.PMULHRSW, Pq, Qq);
    }
            
    return false;
}

private bool FindOpcode0F38_Row0_66()
{
    switch (opcodeByte3)
    {
        case 0x00: return ValidSV13(Opcode.VPSHUFB, Vx, Hx, Wx);
        case 0x01: return ValidSV13(Opcode.VPHADDW, Vx, Hx, Wx);
        case 0x02: return ValidSV13(Opcode.VPHADDD, Vx, Hx, Wx);
        case 0x03: return ValidSV13(Opcode.VPHADDSW, Vx, Hx, Wx);
        case 0x04: return ValidSV13(Opcode.VPMADDUBSW, Vx, Hx, Wx);
        case 0x05: return ValidSV13(Opcode.VPHSUBW, Vx, Hx, Wx);
        case 0x06: return ValidSV13(Opcode.VPHSUBD, Vx, Hx, Wx);
        case 0x07: return ValidSV13(Opcode.VPHSUBSW, Vx, Hx, Wx);

        case 0x08: return ValidSV13(Opcode.VPSIGNB, Vx, Hx, Wx);
        case 0x09: return ValidSV13(Opcode.VPSIGNW, Vx, Hx, Wx);
        case 0x0A: return ValidSV13(Opcode.VPSIGND, Vx, Hx, Wx);
        case 0x0B: return ValidSV13(Opcode.VPMULHRSW, Vx, Hx, Wx);
        case 0x0C: return ValidVexOnly(Opcode.VPERMILPS, Vx, Hx, Wx);
        case 0x0D: return ValidVexOnly(Opcode.VPERMILPD, Vx, Hx, Wx);
        case 0x0E: return ValidVexOnly(Opcode.VTESTPS, Vx, Wx);
        case 0x0F: return ValidVexOnly(Opcode.VTESTPD, Vx, Wx);
    }
            
    return false;
}



#5286743 C++ Rotating 2D shape in list

Posted by Nypyren on 13 April 2016 - 02:07 PM

Nice!

Cleanup suggestions:

You can usually combine variable declarations (like "int x") with the first line that assigns to that variable:

int x = (*tr).getX() - centroid.getX();
Dereferencing a pointer and accessing a member "(*tr)." can usually be replaced with the -> shorthand:

int x = tr->getX() - centroid.getX();



#5286738 DLL access mechanism between multiple applications

Posted by Nypyren on 13 April 2016 - 01:52 PM

So when talking about copy-on-write, and how applications share the dll as read memory, and create a new page of memory when writing to the dll, how does that translate to the actual content of the dll? Is that like separating logic flow from variables?


DLLs contain the code, and a small subset of the variables that the program will actually use. Most variables are created on the stack, or on the heap. Both stack and heap are created dynamically and are not shared between processes. The operating system gives new processes their initial stack when they start, and then the code inside the program is free to request a heap and more stacks any time it wants.

The variables inside a DLL are globals (static variables) and read-only globals.
 

I guess to clarify, what would be a real example of an action inside an application that would trigger the OS to copy and create a new page of memory in the DLL?


The way copy-on-write works is the OS marks certain pages (the typically-4096-byte-chunks I mentioned in my previous post) with special flags using the VirtualProtect function. The flags can be set to cause exceptions to fire whenever the process read/write/executes from memory within that page. For copy-on-write, only write accesses cause the exception. Read and Execute work normally without any changes, since those are safe to do on shared memory.

The exception handler can then handle this by creating a copy of that page BEFORE the write actually happens, change the page protection on the new copy, and then allowing the write to continue where it left off, which now accesses the new copy instead.

Quick Summary: Any attempted write operation (of any kind) to a shared page triggers copy-on-write for that page.

This means that even if the DLL doesn't need to be relocated, if it has any global variables that get modified, the page containing that variable will be copied the moment the program first writes to it.

This stuff about triggering an exception when a page is accessed is built into the processor itself, and the OS handles it. If you're interested, you can search around for "x86 protected mode" and "x86 virtual memory" for even lower-level details (specifically the "descriptor table" stuff). Other processor architectures often have similar support that works in kind of the same ways.


Fun tangental note: The same page access exception stuff is used to detect and handle stack overflows!


#5286722 DLL access mechanism between multiple applications

Posted by Nypyren on 13 April 2016 - 12:38 PM

Overly simplified explanation with some exceptions and special cases omitted for simplicity's sake:

So, the DLL consists of code and some read-only data that are shared (basically the same stuff that it has as a file on disk is what can be shared by multiple processes in RAM).

The dynamic allocations it makes when loaded in a process are NOT shared (and these allocations are also the vast majority of a process's memory consumption, at least for games and "big" programs).

In cases where the DLL's shared memory might be modified by one process, it uses https://en.wikipedia.org/wiki/Copy-on-write to give that process a unique writable area so that other processes are not affected.

https://msdn.microsoft.com/en-us/library/windows/desktop/aa366785(v=vs.85).aspx
 

DLLs are created with a default base address. Every process that uses a DLL will try to load the DLL within its own address space at the default virtual address for the DLL. If multiple applications can load a DLL at its default virtual address, they can share the same physical pages for the DLL. If for some reason a process cannot load the DLL at the default address, it loads the DLL elsewhere. Copy-on-write protection forces some of the DLL's pages to be copied into different physical pages for this process, because the fixes for jump instructions are written within the DLL's pages, and they will be different for this process. If the code section contains many references to the data section, this can cause the entire code section to be copied to new physical pages.


Gory details: At compile time, DLLs and EXEs have some pointers which point to other areas within the EXE/DLL. These pointers are set as if the EXE/DLL were located at a specific position in memory (in the above MSDN quote, this is the "virtual default address" they mention).

Now, since EXEs and DLLs are all compiled separately, none of them know what the others' default virtual address is. So it's possible that when you go to start your process and load all your DLLs, some will have conflicting defaults (meaning, if it just loaded them all, some would overlap in memory). When this happens, it's first-come-first-serve, and DLLs that load later need to be put in a DIFFERENT position in memory instead. This requires "relocating" the DLL - all of those pointers that were created inside the DLL at compile time have to be updated to point to where the DLL is ACTUALLY loaded.

Since this involves updating those pointers, it can no longer be shared with different processes. So it makes a fresh copy of the DLL in that one process's memory so that it can relocate it without affecting any other processes.


TL;DR: DLL physical pages can be shared unless they are relocated, in which case a new set of physical pages is created for that case.


#5286718 C++ Rotating 2D shape in list

Posted by Nypyren on 13 April 2016 - 12:31 PM

Scratch that, I was getting caught in an infinite loop because I was starting at the begin() of list instead off end(), so I would increment forward and then push back thus getting stuck if I'm correct. Now i start at the end() and -- and get my output of added x and y to my list.


Good, you found the infinite loop problem yourself!

Personally, I would not add new vertices to the vector that you're reading from. I would write results to a second vector instead, so that I could preserve the original vertices.


#5286691 C# and Java interop

Posted by Nypyren on 13 April 2016 - 10:29 AM

jd-gui and ILSpy are the decompilers I use for Java and C#, respectively. Both have source code available that you can use.


#5286568 C++ Rotating 2D shape in list

Posted by Nypyren on 12 April 2016 - 09:34 PM

Just for fun...


Dammit, why did I never learn cool properties of complex numbers when I was in school?!

I feel like there's all this awesome math I could be using, but the courses I took never introduced any of this.




PARTNERS