dinsdag 24 december 2019

Programmeren in x86-64 assembly met MASM en Visual Studio.

x86-64 is een door AMD ontworpen evolutie van de Intel 8086 en ideaal om innerloops te optimaliseren van een Visual Studio C/C++ programma.

> Registers
Register                Gebruik
rax, eax, ax, ah, al : accumulator (arithmetic operations)
rbx, ebx, bx, bh, bl : base (starting address of data structures)
rcx, ecx, cx, ch, cl : count (loops, bit shift counts)
rdx, edx, dx, dh, dl : data (arithmetic operations)
rsi, esi, si, sil    : source index
rdi, edi, di, dil    : destination index
rbp, ebp, bp, bpl    : basepointer (frame pointer into stack)
rsp, esp, sp, spl    : stackpointer (offset of current top of stack)
r8, r8d, r8w, r8b    : general purpose register.
r9-r15 idem.
float registers: zmm0-zmm15(AVX-512), ymm0-ymm15(AVX) of xmm0-xmm15(SSE)

> Veelgebruikte instructies
   mov rax,0  ; Codebytes: 48 C7 C0 00 00 00 00
En meteen is hier een opmerking te plaatsen. mov eax,0 doet namelijk hetzelfde met 2 codebytes minder.
   mov eax,0  ; Codebytes: B8 00 00 00 00
Je denkt nu: maar eax is een 32 bits register! Maar in x64 genereren 32-bit operatie's een 32-bit resultaat die zero-extended wordt naar 64-bit. mov eax,0 zet dus allemaal nullen in de meest significante 32 bits van het 64-bit register rax. Dit is gedaan omdat anders bij iedere 32-bit instructie gewacht moet worden op de merge met de 32 meest significante bits, terwijl die in de meeste gevallen niet relevant zijn in de code.
   xor eax, eax ; Codebytes: 31 C0

rax en rdx zijn gevaarlijke registers, want met een MUL of DIV instructie worden zij gebruikt om het resultaat in op te slaan.

> Complexe addressing mogelijkheden
mov eax, [rsi+rcx*4+10]
mov eax, [register][register*scalefactor]+displacement
De scalefactor kan 2,4 of 8 zijn. Displacement een 64 bit waarde.

> Segment registers cs(code), ds(data), ss(stack) en es(extra) zijn niet meer interessant. MASM gebruikt het flat memory model bij x64.

> x86-64 is little endian, dat betekent dat "mov qword ptr[r12], 0fedcba9876543210h" wordt opgeslagen in het geheugen als: 10 32 54 76 98 ba dc fe

> Processor flags
CF = carry flag     = bit 0   rotate 'carry out', or overflow/underflow
PF = parity flag    = bit 2   when number of 1's is even in low-order byte of result
AF = auxiliary flag = bit 4   when carry in least sign. 4-bit digit.
ZF = zero flag      = bit 6   when result is zero
SF = sign flag      = bit 7   when result is negative
TF = trap flag      = bit 8   debugging purposes
IF = interrupt flag = bit 9   if 1 then interrupts are responded
DF = direction flag = bit 10  1 = string dir. is towards lower addresses
OF = overflow flag  = bit 11  foutvlag voor getallen met teken

> Processor flags voorwaardelijke jumps.
Meestal doe je eerst een "cmp", bijvoorbeeld "cmp rax, 3". RAX is dan de destination.
ja   spring als destination is hoger             cf=0 en zf=0
jae  spring als destination is hoger of gelijk   cf=0
jb   spring als destination is lager             cf=1
jbe  spring als destination is lager of gelijk   cf=1 of zf=1
je   spring als destination is gelijk            zf=1
*jg  spring als destination is groter            zf=0 en (sf=of)
*jge spring als destination is groter of gelijk  sf=of
*jl  spring als destination is kleiner           sf<>of
*jle spring als destination is kleiner of gelijk zf=1 of (sf<>of)
* = van toepassing op twee-complement
jc   spring als carry gezet       cf=1
jo   spring als overflow gezet    of=1
js   spring als negatief          sf=1
jz   spring als nul               zf=1
al deze instructie's kunnen ook negatief gebruikt worden, b.v. jnge of jns
special: jecxz/jrcxz spring als ecx/rcx is zero.

> Cache
CPU register: 1 clockcycle
L1 Cache: 3 clockcycles.  (64 bytes per cacheline)
L2 Cache: 15 clockcycles.
L3 Cache: 60 clockcycles.
DRAM: 150 clockcycles.
Conclusie: Probeer het raadplegen van geheugen dus zoveel mogelijk sequentieel(horizontaal) te doen.

> Size naamgeving
MOVSQ verplaatst 8 bytes. De naam "quad" doet je denken dat je 4 bytes verplaatst. Dat klopt dus niet, het staat voor quad words. 4 * 2 bytes = 8 bytes = 64 bits. MOVSD vervplaatst een double word, dus 32 bits = 4 bytes. MOVSW verplaatst 16 bits = 2 bytes.

> Interne CPU optimalisatie
Een "rep movsb" is even snel als "rep movsq" sinds Haswell (4770). "Rep movsb" gebruikt intern 256-bit.
Draai je de volgende code:
cvtsi2ss xmm4, rcx
cvtsi2ss xmm5, r11
divss xmm4, xmm5
movss xmm4, real4 ptr [rcx*4+r10]  ; precalculated value in xmm4
... dan voert de CPU de divss mnemonic niet uit. Slim!


> Assembler toevoegen in een Visual Studio C/C++ programma
Als je een nieuw C++ project aanmaakt in Visual Studio dan kun je de rechtermuis knop gebruiken op het project. Je kunt dan onder "Build Dependencies" de optie "Build Customizations" selecteren. Vink in de dialog "masm (.targets, .props)" aan. Vervolgens kun je .asm files gebruiken in je C/C++ programma.


> Aanroepen van MASM functie's vanuit C/C++
Een functie die in MASM gedefinieerd is kun je in C/C++ declareren op de volgende manier:
extern "C" void ASM_proc(void* par1, Sint64 par2);

Variabelen kun je in C/C++ zo definieeren:
extern "C" void* objVoidP = NULL;
extern "C" Sint64 objSInt64 = NULL;

Vervolgens kun je in de MASM file op de volgende manier naar de variabelen verwijzen:
extern objVoidP: qword
extern objSInt64: qword
(een externe functie kun je in MASM definieeren met "extern remoteProc: proc")

Registers met waarden die je niet hoeft te behouden zijn: rax, rcx, rdx, r8-r11. Andere registers moet je herstellen als je de functie verlaat.
xmm0-xmm5(including) hoef je ook niet te behouden.

Extern "C" aanroep conventie:
parameter1 = rcx/xmm0
parameter2 = rdx/xmm1
parameter3 = r8/xmm2
parameter4 = r9/xmm3
Bij een mix van integers en floats worden registers overgeslagen. Voorbeeld: func3(int a, double b, int c, float d); ->  a in RCX, b in XMM1, c in R8, d in XMM3
De rest van de parameters worden op de stack opgeslagen.


> MASM assembler file layout

extern main_pointer1: qword

.data
align 16
tmpfloat real4 201.0
tmpdouble real8 402.0

.code

ASM_proc proc
 push rbp
 mov rbp, rsp
 sub rsp, 20h        ; 4 qwords space for the 4 parameters.
 mov [rbp-8h], rcx   ; local storage of par1
 mov [rbp-10h], rdx  ; local storage of par2
 mov [rbp-18h], r8   ; local storage of par3
 mov [rbp-20h], r9   ; local storage of par4
 mov rax, [rbp+30h]  ; parameter 5
 mov rax, [rbp+38h]  ; parameter 6
 
    push rdi
    mov rdi, main_pointer1
    mov eax, 3
    cmp eax, 04h
    jnc @F              ; @B voor de voorgaande @@
 mov 
    mov eax, -1
@@:
    pop rdi 
 
 mov rsp, rbp
 pop rbp
 ret
ASM_proc endp

end


> MASM datatypes
    BYTE - 8 bit unsigned integer
    SBYTE - 8 bit signed integer
    WORD - 16 bit unsigned integer
    SWORD - 16 bit signed integer
    DWORD - 32 bit unsigned integer
    SDWORD - 32 bit signed integer
    FWORD - 48 bit integer
    QWORD - 64 bit integer
    TBYTE - 80 bit (10 byte) integer
    REAL4 - 32 bit (4 byte) short real
    REAL8 - 64 bit (8 byte) long real
    REAL10 - 80 bit (10 byte) extended real

> MASM
eerste_byte   equ   this byte
woord_tabel   dw    100 DUP(?)
creeert eerste_byte en geeft deze een byte-attribuut met hetzelfde adres als
woord_tabel. hetzelfde kan bereikt worden met:
eerste_byte   equ   byte ptr woord_tabel

- mov [rbx],0 laat niet weten of een byte, word, dword of qword in [rbx] moet worden
  geplaatst. Doe dat met mov dword ptr [rbx], 0

- hexadecimale getallen worden weergegeven met eerst een nul en op het
  eind een h. dus b.v. 02f35h

zaterdag 16 november 2019

Sleeping a Thread in a realtime game

In my SDL 2 racing game, I use 2 threads and they use approx. 25% of the CPU power. The CPU speed is 3.80 Ghz in this case. The game runs realtime and in every frame the code uses approx. 5 - 7 ms.

I tested using 3 threads and then the code uses approx. 3 - 5 ms in each frame. So, 3 threads is faster. However, the CPU utilization becomes 37%! And that is on my PC. Other slower PC's might get a higher CPU utilization.

So, I thought, maybe I can use a Mutex to let the operating system use the threads in the remaining time for each frame that is not used.
It worked. I used EnterCriticalSection(pCritSec) and LeaveCriticalSection(pCritSec). The CPU utilization goes down to 12%. A profit of 25% CPU utilization!

However, the CPU speed clocks down from 3.8 Ghz to 2.0 Ghz and the code speed for each frame becomes unpredictable: 3 - 13 ms, and that is unacceptable.

Then I did not use the mutex on one thread of the 3. The result was that the CPU runs at 3,80 Ghz but the CPU utilization is 25%. The code speed is 4 - 6 ms per frame. This seems to be the best solution.

zaterdag 2 november 2019

Modern processor rep movsb performance

According to https://stackoverflow.com/questions/43343231/enhanced-rep-movsb-for-memcpy, the assembler instruction "rep movsb" has been enhanced since Haswell (4770)  to use 256-bit operations internally.

I notice that when coding assembly: The "rep movsq" is as fast as "rep movsb". It is a bit counter-intuitive. You tend to think assembly is the lowest level of optimization, but that is not the case: the bytecode interpreter also does optimization.

The naming of the instructions are confusing: movsq moves 8 bytes.  The name Quad makes you think that 4 bytes are moved. No, that is done by the movsd instruction which stands for move double word.
Double makes you think that 2 bytes are moved. No, that is done by the movsw instruction which stands for move word.
In the 16-bit era a "word" was the native unit of the processor. That's fine with me, but calling 32 bits a double word is confusing. They should have named it Quad, and 8 bytes an Oct.

dinsdag 8 oktober 2019

C game: fastest way to sort 2d objects by distance

Sorting the 2d objects in my SunRacer game took 6300 counts with QuickSort. It was strange to me that a nearly sorted array was not sorted any faster. So I went to look for a better sort algorithm.
At this moment, I'm using the bucket sort algorithm, and sort each bucket with the insertion sort algorithm. Sorting the objects now take 185 counts according the SDL2 performance counter.

More details:
In my game, objects can have a distance from 0 to 1000.
I created 40 buckets and move all objects in one of these 40 buckets. This way, I have 40 small buckets with objects which are near to eachother. Sorting these buckets is faster because there are not many objects in each bucket. Insertion sort becomes slow on big arrays (50+ elements). My buckets are max. 50 elements.

The fastest way to place objects in a bucket is to compare each time with the half of the remaining buckets.
if (element->distance < 500) {
  if (element->distance < 250) { ... }
  else { .... }
} else {
  if (element->distance < 750) { ... }
  else { ...  }
}


Snellere atan2

float Globals::ApproxAtan2(float y, float x)
{
    const float n1 = 0.97239411f;
    const float n2 = -0.19194795f;
    float result = 0.0f;
    if (x != 0.0f)
    {
        const union { float flVal; Uint32 nVal; } tYSign = { y };
        const union { float flVal; Uint32 nVal; } tXSign = { x };
        if (fabsf(x) >= fabsf(y))
        {
            union { float flVal; Uint32 nVal; } tOffset = { PI_FLOAT };
            // Add or subtract PI based on y's sign.
            tOffset.nVal |= tYSign.nVal & 0x80000000u;
            // No offset if x is positive, so multiby 0 or based on x's sign.
            tOffset.nVal *= tXSign.nVal >> 31;
            result = tOffset.flVal;
            const float z = y / x;
            result += (n1 + n2 * z * z) * z;
        }
        else // Use atan(y/x) = pi/2 - atan(x/y) if |y/x| > 1.
        {
            union { float flVal; Uint32 nVal; } tOffset = { PI_2_FLOAT };
            // Add or subtract PI/2 based on y's sign.
            tOffset.nVal |= tYSign.nVal & 0x80000000u;
            result = tOffset.flVal;
            const float z = x / y;
            result -= (n1 + n2 * z * z) * z;
        }
    }
    else if (y > 0.0f)
    {
        result = PI_2_FLOAT;
    }
    else if (y < 0.0f)
    {
        result = -PI_2_FLOAT;
    }
    return result;
}

donderdag 3 oktober 2019

Turn off green lines Visual Studio

When you open a C/C++ project in Visual Studio 2019, a lot of green lines (squiggles) appear.
This can be turned off:
Options->Text Editor->C/C++-> Advanced-> Code Analysis-> Disable C++ Code Analysis Experience.

zondag 22 september 2019

C type declarations


De volgende C type declarations gebruik ik (onder andere) in m'n motorracer spel:

Declaratie: void (*manuallyDrawElement)(void*);
Uitleg: manuallyDrawElement is pointer to a function that takes a void* as argument.

Declaratie: Position_VisibleGrid (*fovVisibleGridPrecalculated)[100] = NULL;
Uitleg: fovVisibleGridPrecalculated is pointer to array[100] of Position_VisibleGrid

Vind je dit lastig? Gebruik de volgende webpagina's voor extra uitleg:
Reading C declarations
C operator precedence
Function pointer to member function in C++

zaterdag 3 augustus 2019

Extract pictures Duke Nukem 3D

The next way, you can extract pictures from Duke Nukem 3D:

  • Use QuickBMS to extract the .art files. The Duke_Nukem_3d_GRP.bms script can be found on the site.
    quickbms_4gb_files.exe Duke_Nukem_3d_GRP.bms Duke3d.grp result_sub_dirsub
  • Use BAFed to open an .art file and look at all the images that are contained in it. Using BAFed, you can also save an image as .PNG.

zondag 14 juli 2019

SCP gebruiken om foto's van android telefoon af te krijgen.

Stel, je hebt een android telefoon waarbij je niet simpel met een kabeltje kunt connecten om je foto's eraf te halen. Je wilt ook geen gebruik maken van dropbox.

Gebruik dan SimpleSSHD (icoon vallend beertje, ofwel dropbear) op android (via de playstore)

Zo haal je dan je foto's en whatsapp images en video's eraf:
scp -P 2222 -r 192.168.0.30:/storage/emulated/0/DCIM/Camera camera
scp -P 2222 -r 192.168.0.30:/storage/emulated/0/WhatsApp/Media whatsappmedia

Met TotalCommander voor Android kun je opzoeken wat de paden zijn naar de bestanden die je wil.

woensdag 29 mei 2019

C/C++: string redemption

Het concept van string (oftewel char*) is een enorm drama in C/C++.
Redenen:
1) char (byte) is niet genoeg om UTF-8 strings te bevatten.
2) als je strings wilt samenvoegen, dan moet je eerst vantevoren gokken hoeveel characters je nodig hebt. Meestal komt het uit op een (te) ruime allocatie, bijvoorbeeld char[255], terwijl je er bijvoorbeeld maar 60 nodig hebt. Die ruimte wordt gereserveerd op de stack en die loopt sneller vol. Dus echt hardcore string crunching kun je op die manier niet doen, want dan krijg je een stackoverflow.
3) als er geen \0 aan het einde van de string staat, dan heb je een bufferoverrun te pakken. Een manier die hackers gebruiken om toegang tot je geheugen te krijgen.
4) beperkte C string library functions, waardoor bijvoorbeeld Howard Chu terecht klaagt over coderegels zoals: strcat(strcat(strcat(strcpy(buf, "This "),"is "),"a long "),"string.");

De beste vervangende string library voor C die ik gevonden heb is The Better String Library.

zaterdag 25 mei 2019

Sound Libraries

Als je een geluid wilt afspelen in je SDL 2 game, heb je verschillende optie's:
1) Gebruik de SDL_Mixer die bij SDL2 wordt geleverd. Maar een pitch of snelheid van een sample kun je niet aanpassen.
2) Gebruik https://github.com/RandyGaul/cute_headers. Daarmee kun je de pitch van een sample aanpassen, maar weer niet de speed.
3) Gebruik http://sol.gfxile.net/soloud/quickstart.html. Deze library gebruik ik, omdat je de speed van een sample kunt aanpassen en het heeft een goede interface met OpenMPT, zodat je amiga modules kunt afspelen.

woensdag 8 mei 2019

Low level x64 optimalisatie

In een game heb ik een innerloop die 5.9ms duurt met o.a. de volgende instructie's:
cvtsi2ss xmm4, rcx
cvtsi2ss xmm5, r11
divss xmm4, xmm5

Breid ik de loop uit met het ophalen van een precalculated value, dan verwacht ik dat de innerloop langer duurt dan 5.9ms:
cvtsi2ss xmm4, rcx
cvtsi2ss xmm5, r11
divss xmm4, xmm5
movss xmm4, real4 ptr [rcx*4+r10]  ; precalculated value in xmm4
Maar de innerloop duurt nu nog maar 4.1ms!

Dit betekent dat de processor zelf doorheeft dat xmm4 na de divss instructie opnieuw wordt gevuld, dus skipt hij de divss instructie. Slim!

Zowel in SSE(x64 SIMD) als normale x64 blijkt een DIV zeer traag te zijn. In dit geval zorgt de enkele DIVSS-instructie voor 1.8ms vertraging in m'n innerloop. Precalculation is dus de moeite waard.
Aangezien je in SSE floating point berekeningen niet shift kunt gebruiken voor een deling, moet je erg opletten wat je doet.

zaterdag 20 april 2019

faster sinus

https://gamedev.stackexchange.com/questions/4779/is-there-a-faster-sine-function
https://web.archive.org/web/20100613230051/http://www.devmaster.net/forums/showthread.php?t=5784

float Globals::SinusPrecision(float x) {
    // high precision sine
    float sin;

    //always wrap input angle to -PI..PI
    if (x < -3.14159265f) {
        x += 6.28318531f;
    }
    else {
        if (x > 3.14159265f) {
            x -= 6.28318531f;
        }
    }

    if (x < -3.14159265f || x > 3.14159265f) {
        int w = 1;
    }

    if (x < 0)
    {
        sin = 1.27323954f * x + .405284735f * x * x;

        if (sin < 0)
            sin = .225 * (sin * -sin - sin) + sin;
        else
            sin = .225 * (sin * sin - sin) + sin;
    }
    else
    {
        sin = 1.27323954f * x - 0.405284735f * x * x;

        if (sin < 0)
            sin = .225f * (sin * -sin - sin) + sin;
        else
            sin = .225f * (sin * sin - sin) + sin;
    }

    return sin;
}
Of deze http://web.archive.org/web/20110925033606/http://lab.polygonal.de/2007/07/18/fast-and-accurate-sinecosine-approximation/

CPU cache en snelheid

Snelheden van geheugen toegang van de CPU:

> CPU register: 1 clockcycle
> L1 Cache: 3 clockcycles.
> L2 Cache: 15 clockcycles.
> L3 Cache: 60 clockcycles.
> DRAM: 150 clockcycles.

L1 cache is dus de snelste en is soms wel 320 k groot. Behoorlijk dus.
L1 cache doet 64 bytes per line. Dit betekent dat iedere keer dat DRAM wordt opgehaald er automatisch 64 bytes in de cache terecht komen.
Het is dus belangrijk om ervoor te zorgen dat gegevens die je nodig hebt, sequentieel achter elkaar staan.

dinsdag 16 april 2019

Correcting "Pseudo 3D Planes aka MODE7"

The formula David uses in his Programming Pseudo 3D Planes aka MODE7 (C++) is not correct.
He uses:

   for (int y = 0; y < ScreenHeight() / 2; y++)
   {
      float fSampleDepth = (float)y / ((float)ScreenHeight() / 2.0f);
      float fStartX = (fFarX1 - fNearX1) / (fSampleDepth) + fNearX1;
      float fStartY = (fFarY1 - fNearY1) / (fSampleDepth) + fNearY1;
      float fEndX = (fFarX2 - fNearX2) / (fSampleDepth) + fNearX2;
      float fEndY = (fFarY2 - fNearY2) / (fSampleDepth) + fNearY2;

      for (int x = 0; x < ScreenWidth(); x++)
      {
      ...

In the comments a guy named JohnnyCrash pointed out that David does not draw the Frustum, but from infinity till the far plane. Also he doesn't expect to see weird curvature when you change the near plane.

There is a fellow dutchman who wrote a very nice explanation about mode7. Read it.
Which will result in the correct way of drawing:

float space_y = 100.0f;
float scale_y = 200.0f;
int horizon = 40; 
for (int y = 0; y < Graphics::ScreenHalfDIMy; y++) {
 float distance = space_y * scale_y / (y + horizon);

 float fStartX = fWorldX + (cosf(fWorldAngle + fFoVHalf) * distance);
 float fStartY = fWorldY - (sinf(fWorldAngle + fFoVHalf) * distance);
 float fEndX = fWorldX + (cosf(fWorldAngle - fFoVHalf) * distance);
 float fEndY = fWorldY - (sinf(fWorldAngle - fFoVHalf) * distance);

 for (int x = 0; x < Graphics::ScreenDIMx; x++) {
  float fSampleWidth = (float)x / (float)Graphics::ScreenDIMx;
  float fSampleX = fStartX + ((fEndX - fStartX) * fSampleWidth);
  float fSampleY = fStartY + ((fEndY - fStartY) * fSampleWidth);

  Uint32 pixelColor = racetrackARGB.GetPixel(fSampleX, fSampleY);
  screenARGB.SetPixel(x, y, pixelColor);
 }
}

Which results in the following screenshot, which draws the 3d mode7 at the top and the 2d with frustum at the bottom:

zaterdag 30 maart 2019

Postgresql json datatype

https://stackoverflow.com/questions/36141388/how-can-i-get-all-keys-from-a-json-column-in-postgres

select jt.*
from (select key as key, value as valuestr, count(*) over () as metadata_aantal from json_each_text('{"Key1":"Value1", "Key2":"Value2"}'::json)) jt

https://www.postgresql.org/docs/11/functions-json.html

select * from table t where t.jsonbcolumn->'nummer' = '22420';

select id from aTable where last_message->'Gegevens'->>'prio' = '1' order by id

jsonb veld aanpassen:
SELECT '{"a":1}'::jsonb || '{"b": 1}'::jsonb;

https://stackoverflow.com/questions/18209625/how-do-i-modify-fields-inside-the-new-postgresql-json-datatype

update table set data = '{"Key1": true, "Key2": "yoho"}'::jsonb where id = 'theid001';

donderdag 7 maart 2019

C# HttpClient https without certificate

var spHandler = new HttpClientHandler()
{
      ServerCertificateCustomValidationCallback = delegate
      {
          return true;
      }
};
var hc = new System.Net.Http.HttpClient(spHandler);
var response = await hc.GetAsync("https://adress");

zondag 6 januari 2019

vrijdag 4 januari 2019

Upload folder with .NET Core and JQuery

Client (javascript) :
let uploadInput = $('<input type="file" id="file" name="file" size="10" accept="image/*" webkitdirectory mozdirectory />');

        function uploadButtonClick() {
            let promiseArray = [];
            let theFiles = $('#file')[0].files;
            for (let i = 0; i < theFiles.length; i++) {
                let aFile = theFiles[i];
                (function (theFile) {
                    promiseArray.push(uploadFile(theFile));
                })(aFile);
            }
            $.when.apply($, promiseArray).then(function () {
            });
        }

        function uploadFile(theFile) {
            let deferred = $.Deferred();
            var formData = new FormData();
            formData.append('parameter1', 'value1');
            formData.append('file', theFile, theFile.webkitRelativePath);
            $.ajax({
                method: "POST",
                url: '/v1/UploadFile',
                data: formData,
                cache: false,
                contentType: false,
                processData: false
            }).done(function (response) {
                deferred.resolve(response);
            });
            return deferred.promise();
        }

Server (.NET Core controller):

        [HttpPost]
        [RequestFormLimits(MultipartBodyLengthLimit =209715200)]
        [RequestSizeLimit(209715200)]
        [Route("UploadFile")]
        public ActionResult<List<string>> UploadFile(IFormFile file, [FromServices] IHostingEnvironment env)
        {
            string uploadDir = "c:\\dir\\upload";
            string dirPart = Path.GetDirectoryName(file.FileName);
            Directory.CreateDirectory(uploadDir + "\\" + dirPart);

            string fileNamePart = file.FileName; // Path.GetFileName(file.FileName);
            string fileName = $"{uploadDir}\\{fileNamePart}";
            using (FileStream fs = System.IO.File.Create(fileName))
            {
                file.CopyTo(fs);
                fs.Flush();
            }
            var result = new List<string>();
            result.Add($"{file.Length} bytes uploaded successfully.");
            return result;
        }

dinsdag 1 januari 2019

Organisatie moet zelf ook scalable en non-blocking zijn, net als de software die het gebruikt.

Wil je een gedistribueerd, scalable, asynchroon, non-blocking software gebruiken, dan moet je organisatie ook zo werken, anders gaat het geen rendement geven.
Dus geen dead-locks, zoals grote meetings en conference calls waarin iedereen veel tijd kwijt is.
Inspiratie van Pieter Hintjens van ZeroMQ uit: https://www.youtube.com/watch?v=yhGXJ9Jt3-A